最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

golang中進(jìn)行條件編譯的示例詳解

 更新時(shí)間:2025年03月31日 10:51:54   作者:apocelipes  
條件編譯顧名思義就是在編譯時(shí)讓代碼中的一部分生效或者失效,從而控制編譯時(shí)的代碼執(zhí)行路徑,進(jìn)而影響編譯出來(lái)的程序的行為,下面我們就來(lái)看看如何在golang中進(jìn)行條件編譯吧

寫c/c++或者rust的開(kāi)發(fā)者應(yīng)該對(duì)條件編譯不陌生,條件編譯顧名思義就是在編譯時(shí)讓代碼中的一部分生效或者失效,從而控制編譯時(shí)的代碼執(zhí)行路徑,進(jìn)而影響編譯出來(lái)的程序的行為。

這有啥用呢?通常在編寫跨平臺(tái)代碼的時(shí)候有用。比如我想開(kāi)發(fā)一個(gè)文件操作庫(kù),這個(gè)庫(kù)有全平臺(tái)統(tǒng)一的接口,然而各大操作系統(tǒng)提供的文件和文件系統(tǒng)api百花齊放,我們沒(méi)法只用一套代碼就讓我們的庫(kù)能在所有的操作系統(tǒng)上正常運(yùn)行。

這時(shí)候就需要條件編譯出場(chǎng)了,在Linux上我們只讓適配了Linux的代碼生效,在Windows上則只讓W(xué)indows相關(guān)的代碼生效其他失效。比如:

#ifdef _Windows
typedef HFILE file_handle
#else
typedef int file_handle
#endif
 
file_handle open_file(const char *path)
{
    if (!path) {
#ifdef _Windows
        return invalid_handle;
#else
        return -1;
#endif
    }
 
#ifdef _Windows
    OFSTRUCT buffer;
    return OpenFile(path, &buffer, OF_READ);
#else
    return open(path, O_RDONLY|O_CLOEXEC);
#endif
}

在這個(gè)例子里,Windows和Linux的api完全不同,為了隱藏這種不同我們用條件編譯在不同平臺(tái)上定義出了一組相同的接口,這樣我們就無(wú)需關(guān)心平臺(tái)差異了。

從上面的例子也可以看出,c/c++實(shí)現(xiàn)條件編譯最常用的是依靠宏。通過(guò)在編譯時(shí)指定特定平臺(tái)的標(biāo)識(shí),這些預(yù)編譯宏就能自動(dòng)把不需要的代碼剔除不進(jìn)行編譯。c和c++中另一種實(shí)現(xiàn)條件編譯的做法是依賴構(gòu)建系統(tǒng),我們不再使用預(yù)編譯宏,但會(huì)為每個(gè)平臺(tái)都編寫一份代碼:

// open_file_windows.c
typedef HFILE file_handle
 
file_handle open_file(const char *path)
{
    if (!path) {
        return invalid_handle;
    }
 
    OFSTRUCT buffer;
    return OpenFile(path, &buffer, OF_READ);
}
 
// open_file_linux.c
typedef int file_handle
 
file_handle open_file(const char *path)
{
    if (!path) {
        return -1;
    }
 
    return open(path, O_RDONLY|O_CLOEXEC);
}

然后指定構(gòu)建系統(tǒng)在編譯Linux程序時(shí)只使用open_file_linux.c,在Windows上則只使用open_file_windows.c。這樣同樣可以把和當(dāng)前平臺(tái)無(wú)關(guān)的不兼容的代碼排除掉?,F(xiàn)在的構(gòu)建系統(tǒng)如meson,cmake都可以輕松實(shí)現(xiàn)上述功能。

自稱系統(tǒng)級(jí)的golang,自然也是支持條件編譯的,而且它支持的方式是靠第二種——即依靠構(gòu)建系統(tǒng)。

想要在golang中使用條件編譯,也有兩種辦法。因?yàn)槲覀儾皇褂煤?,也沒(méi)法在編譯時(shí)給go build指定信息哪些代碼不需要,所以需要一些手段來(lái)讓go編譯工具鏈識(shí)別出應(yīng)該編譯和應(yīng)該忽略的代碼。

第一種就是依賴文件后綴名。go的源代碼文件的名字是有特殊規(guī)定的,符合下面格式的文件會(huì)被認(rèn)為是在特定平臺(tái)上需要被編譯的文件:

name_{system}_{arch}.go
name_{system}_{arch}_test.go

其中system的取值和環(huán)境變量GOOS一樣,常見(jiàn)的有windows、linux、darwin、unix,其中后綴是unix時(shí)文件會(huì)在Linux、bsd和darwin這些平臺(tái)上編譯。沒(méi)有明確指定那么該文件就會(huì)在全平臺(tái)有效,除非有額外指定我們后面會(huì)說(shuō)的build tag。

arch的取值和GOARCH環(huán)境變量一樣,都是常見(jiàn)的硬件平臺(tái)比如amd64、arm64、loong64等等。有這些后綴的文件只會(huì)在為特定的硬件平臺(tái)編譯程序時(shí)才會(huì)生效并加入編譯過(guò)程。如果沒(méi)明確指定arch,則默認(rèn)目標(biāo)操作系統(tǒng)的所有支持的硬件平臺(tái)上這個(gè)文件都會(huì)參與編譯。

第一種方法簡(jiǎn)單易懂,但缺點(diǎn)也很明顯,我們需要為每個(gè)平臺(tái)都維護(hù)一份源代碼文件,而且這些文件里必定會(huì)有很多重復(fù)的平臺(tái)無(wú)關(guān)的代碼,這對(duì)維護(hù)來(lái)說(shuō)是個(gè)很大的負(fù)擔(dān)。

所以第一種方案只適合那種平臺(tái)間差異巨大的代碼,一個(gè)典型的例子是go自己的runtime的代碼,因?yàn)閰f(xié)程調(diào)度需要很多操作系統(tǒng)甚至硬件平臺(tái)的功能做輔助,因此runtime在每個(gè)操作系統(tǒng)上出了自己的api之外差異很大,因此使用文件名后綴的形式分成多個(gè)文件維護(hù)是比較合適的。

第二種方法不再使用文件名后綴,而是依賴build tag這種東西來(lái)提示編譯器哪些代碼需要被編譯。

build tag是go的一種編譯指令,用于告訴編譯器該文件需要在什么條件下才需要被編譯:

//go:build 表達(dá)式

tag一般寫在文件的開(kāi)頭(在版權(quán)聲明之后)。其中表達(dá)式是一些tag的名字和簡(jiǎn)單的布爾運(yùn)算符。比如:

1.go:build !windows

表示文件在Windows以外的系統(tǒng)上才編譯

2.go:build linux && (arm64 || amd64)

表示在arm64或者amd64的Linux系統(tǒng)上才編譯這個(gè)文件

3.go:build ignore

特殊tag,表示文件不管在什么平臺(tái)上都會(huì)被忽略,除非明確使用go run、go build或者go generate運(yùn)行這個(gè)文件

4.go:build 自定義tag名

表示只有在`go build -tags tag名`明確指定相同的tag名時(shí)才編譯這個(gè)文件

預(yù)定義的tag的值其實(shí)就是前面文件名后綴那里提到過(guò)的system和arch。可以看到邏輯運(yùn)算符和括號(hào)都可以使用,語(yǔ)義也和邏輯運(yùn)算一樣。使用tag的優(yōu)點(diǎn)在于它可以讓linux和Windows通用的邏輯出現(xiàn)在同一個(gè)文件里而不需要復(fù)制兩份到_windows.go和_linux.go里。更重要的是它允許我們自定義編譯tag。

能自定義tag的話玩法就很多了,我們來(lái)看個(gè)例子,一個(gè)可以在編譯時(shí)指定日志輸出級(jí)別的玩具程序,它的特點(diǎn)在于低于指定級(jí)別的日志不僅不會(huì)輸出,而且連代碼都不會(huì)存在,真正的做到零開(kāi)銷。

通常控制日志輸出級(jí)別都是這么做的:

func DebugLog(msg ...any) {
    if level > DEBUG {
        return
    }
    ...
}
func InfoLog(msg ...any) {
    if level > INFO {
        return
    }
    ...
}

然而這不可避免的需要一次if判斷,如果函數(shù)比較復(fù)雜的話還需要付出一次額外的函數(shù)調(diào)用開(kāi)銷。

使用條件編譯可以消除這些開(kāi)銷,首先是處理debug級(jí)別的日志函數(shù):

// file log_debug.go
//go:build debug || (!info && !warning)
package log
 
import "fmt"
 
func Debug(msg any) {
    fmt.Println("DEBUG:", msg)
}
 
// file log_no_debug.go
//go:build info || warning
package log
 
func Debug(_ any) {}

作為最低的級(jí)別,只有在指定了debug這個(gè)tag以及默認(rèn)情況下才會(huì)生效,其他時(shí)間都是空函數(shù)。

info級(jí)別的處理是一樣的,只有指定級(jí)別為debug和info時(shí)才生效:

// file log_info.go
//go:build !debug && !warning
package log
 
import "fmt"
 
func Info(msg any) {
    fmt.Println("INFO:", msg)
}
 
// file log_no_info.go
//go:build warning
 
package log
 
func Info(_ any) {}

最后是warning級(jí)別,這個(gè)級(jí)別的日志不管在什么時(shí)候都會(huì)輸出,因此它不需要條件編譯所以也不需要tag:

// file log_warning.go
package log
 
import "fmt"
 
func Warning(msg any) {
    fmt.Println("WARN:", msg)
}

最后是main函數(shù):

package main
 
import "conditionalcompile/log"
 
func main() {
    log.Debug("A debug level message")
    log.Info("A info level message")
    log.Warning("A warning level message")
}

因?yàn)槲覀儼巡簧У暮瘮?shù)都寫成了空函數(shù),因此編譯器會(huì)在編譯時(shí)發(fā)現(xiàn)這些空函數(shù)的調(diào)用什么都沒(méi)做,因此直接忽略掉它們,所以運(yùn)行的時(shí)候不會(huì)產(chǎn)生任何額外的開(kāi)銷。

下面簡(jiǎn)單做個(gè)測(cè)試:

$ go run
 
# 輸出
DEBUG: A debug level message
INFO: A info level message
WARN: A warning level message
 
$ go run -tags info .
 
# 輸出
INFO: A info level message
WARN: A warning level message
 
$ go run -tags warning .
 
# 輸出
WARN: A warning level message

和我們預(yù)期的一致。不過(guò)我并不推薦你使用這個(gè)方法,因?yàn)樗枰獮槊總€(gè)日志函數(shù)編寫兩份代碼,而且需要對(duì)編譯tag做很復(fù)雜的邏輯運(yùn)算,非常容易出錯(cuò);而且運(yùn)行時(shí)一次if判斷一般也不會(huì)帶來(lái)太多的性能開(kāi)銷,除非明確定位到了判斷日志級(jí)別產(chǎn)生了不可接受的性能瓶頸,否則永遠(yuǎn)不要嘗試使用上面的玩具代碼。

不過(guò)生產(chǎn)實(shí)踐里真的有使用自定義tag的例子:wire。

依賴注入工具wire讓開(kāi)發(fā)者把需要注入的依賴寫入有特殊編譯tag的源文件,這些源文件正常編譯的時(shí)候不會(huì)被編譯到程序里,使用wire工具生成注入代碼的時(shí)候這些文件才會(huì)被識(shí)別,這樣既可以正常實(shí)現(xiàn)依賴注入功能又不會(huì)對(duì)代碼產(chǎn)生太大的影響。更具體的做法可以去看wire的使用教程。

至于選擇在golang里選擇哪種方式實(shí)現(xiàn)條件編譯,這個(gè)得結(jié)合實(shí)際需求來(lái)看。至少像go自身的代碼以及k8s中兩種方法文件名后綴和build tag都有并行使用,最重要的選擇依據(jù)還是要以方便自己和他人維護(hù)代碼為準(zhǔn)。

到此這篇關(guān)于golang中進(jìn)行條件編譯的示例詳解的文章就介紹到這了,更多相關(guān)go條件編譯內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • golang sql語(yǔ)句超時(shí)控制方案及原理

    golang sql語(yǔ)句超時(shí)控制方案及原理

    一般應(yīng)用程序在執(zhí)行一條sql語(yǔ)句時(shí),都會(huì)給這條sql設(shè)置一個(gè)超時(shí)時(shí)間,本文主要介紹了golang sql語(yǔ)句超時(shí)控制方案及原理,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-12-12
  • 基于Go語(yǔ)言開(kāi)發(fā)篇一個(gè)命令行進(jìn)程監(jiān)控工具

    基于Go語(yǔ)言開(kāi)發(fā)篇一個(gè)命令行進(jìn)程監(jiān)控工具

    在生產(chǎn)和開(kāi)發(fā)環(huán)境中,監(jiān)控關(guān)鍵進(jìn)程的存活與資源使用是非常常見(jiàn)的需求,本篇將開(kāi)發(fā)一個(gè)輕量級(jí)的命令行進(jìn)程監(jiān)控工具,感興趣的小伙伴可以了解下
    2025-09-09
  • go1.21中slog日志包用法入門

    go1.21中slog日志包用法入門

    go1.21中,slog這一被Go語(yǔ)言團(tuán)隊(duì)精心設(shè)計(jì)的結(jié)構(gòu)化日志包正式落地,本文將帶領(lǐng)讀者上手slog,體會(huì)其與傳統(tǒng)log的差異,感興趣的小伙伴快跟隨小編一起學(xué)習(xí)一下吧
    2023-09-09
  • 掌握Golang中的select語(yǔ)句實(shí)現(xiàn)并發(fā)編程

    掌握Golang中的select語(yǔ)句實(shí)現(xiàn)并發(fā)編程

    Golang中的select語(yǔ)句用于在多個(gè)通道間選擇可讀或可寫的操作,并阻塞等待其中一個(gè)通道進(jìn)行操作。可以用于實(shí)現(xiàn)超時(shí)控制、取消和中斷操作等。同時(shí),select語(yǔ)句支持default分支,用于在沒(méi)有任何通道可操作時(shí)執(zhí)行默認(rèn)操作
    2023-04-04
  • Go語(yǔ)言流程控制語(yǔ)句

    Go語(yǔ)言流程控制語(yǔ)句

    這篇文章介紹了Go語(yǔ)言流程控制語(yǔ)句的用法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-07-07
  • Go中各種newreader和newbuffer的使用總結(jié)

    Go中各種newreader和newbuffer的使用總結(jié)

    這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中各種newreader和newbuffer的使用的相關(guān)資料,文中的示例代碼講解詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴可以了解下
    2023-11-11
  • 詳解Golang五種原子性操作的用法

    詳解Golang五種原子性操作的用法

    本文主要介紹了詳解Golang五種原子性操作的用法,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • GORM不定參數(shù)的用法最佳實(shí)踐

    GORM不定參數(shù)的用法最佳實(shí)踐

    這篇文章主要為大家介紹了GORM不定參數(shù)的用法最佳實(shí)踐,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • Go語(yǔ)言中make和new的區(qū)別及說(shuō)明

    Go語(yǔ)言中make和new的區(qū)別及說(shuō)明

    這篇文章主要介紹了Go語(yǔ)言中make和new的區(qū)別及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-07-07
  • 淺析Golang切片截取功能與C++的vector區(qū)別

    淺析Golang切片截取功能與C++的vector區(qū)別

    這篇文章主要介紹了Golang中切片的截取功能與C++中的vector有什么區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧
    2022-10-10

最新評(píng)論

周口市| 阿坝| 定西市| 鹤庆县| 赣榆县| 宽甸| 大庆市| 广平县| 九寨沟县| 南和县| 连云港市| 水城县| 灌南县| 增城市| 闸北区| 绵竹市| 江口县| 洪洞县| 都昌县| 阜阳市| 耿马| 文化| 皋兰县| 南宫市| 甘孜县| 黄浦区| 元阳县| 济阳县| 高邮市| 新安县| 潮安县| 内江市| 鹤峰县| 舟曲县| 顺昌县| 泗阳县| 汝南县| 怀化市| 海晏县| 玛沁县| 泽普县|