成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

充分理解 C/C++ 重要概念:運(yùn)行時(shí)庫(kù)

開(kāi)發(fā)
本文介紹了什么是C/C++運(yùn)行時(shí)庫(kù),運(yùn)行時(shí)庫(kù)的主要的功能,各平臺(tái)的存在形式,以及開(kāi)發(fā)要注意的問(wèn)題,包括多實(shí)例問(wèn)題和多版本問(wèn)題等。

作者 | robot

在C/C++開(kāi)發(fā)領(lǐng)域,運(yùn)行時(shí)庫(kù)(Run Time Library)是一個(gè)非常重要且基礎(chǔ)的概念,但是相關(guān)的介紹文章卻很少,以至于對(duì)很多開(kāi)發(fā)同學(xué)來(lái)說(shuō),這是一個(gè)偏神秘的存在,本文作者查閱了大量資料,并結(jié)合自己的理解,希望能夠通俗易懂的科普和揭秘一下這一領(lǐng)域,內(nèi)容包括什么是C/C++運(yùn)行時(shí)庫(kù),它的主要功能,各平臺(tái)的存在形式,以及開(kāi)發(fā)中要注意的問(wèn)題。

一、認(rèn)識(shí)C/C++運(yùn)行時(shí)庫(kù)

1. 初識(shí)概念

你是否知道,我們開(kāi)發(fā)的C/C++程序,在運(yùn)行過(guò)程中,背后有一個(gè)基礎(chǔ)模塊,在默默提供著支持?

這背后的基礎(chǔ)模塊,也叫基礎(chǔ)庫(kù),或運(yùn)行時(shí)庫(kù)。

見(jiàn)下圖,支撐C/C++程序運(yùn)行的,除了CPU/內(nèi)存等硬件和操作系統(tǒng)之外,還有C/C++運(yùn)行時(shí)庫(kù):

那么,它具體在干什么呢?要理解這一概念,需要先從C/C++語(yǔ)言說(shuō)起。

2. 編程語(yǔ)言

基本每種編程語(yǔ)言,包括C/C++語(yǔ)言,都包括兩個(gè)部分:

  • 語(yǔ)法部分:比如判斷、循環(huán)、定義變量、函數(shù)、類、注釋等,及一些內(nèi)置類型。
  • 標(biāo)準(zhǔn)庫(kù)部分:該語(yǔ)言最常用的函數(shù)或類庫(kù),比如輸入輸出、內(nèi)存管理、字符串操作、數(shù)學(xué)函數(shù)、線程相關(guān)等。因?yàn)樘A(chǔ)常用,所以被納入語(yǔ)言標(biāo)準(zhǔn)的一部分,稱為標(biāo)準(zhǔn)庫(kù)。

狹義的語(yǔ)言,僅僅是指語(yǔ)法部分,可稱之為語(yǔ)言本身;廣義的語(yǔ)言,是指語(yǔ)法部分+標(biāo)準(zhǔn)庫(kù)部分。

語(yǔ)言的標(biāo)準(zhǔn)庫(kù)+各種第三方庫(kù),組成了我們程序中常見(jiàn)的各種庫(kù)。

C/C++運(yùn)行時(shí)庫(kù),就是在運(yùn)行時(shí),為語(yǔ)言的這兩部分提供基礎(chǔ)支持的庫(kù)。或者簡(jiǎn)言之,C/C++運(yùn)行時(shí)庫(kù),就是C/C++程序在運(yùn)行時(shí)依賴的基礎(chǔ)庫(kù)。

3. 其它語(yǔ)言的運(yùn)行時(shí)庫(kù)

除了C/C++語(yǔ)言之外,其它語(yǔ)言其實(shí)也有自己的運(yùn)行時(shí)庫(kù)。

比如Java,其運(yùn)行時(shí)庫(kù)就是Java運(yùn)行時(shí)環(huán)境(JRE),它包括Java虛擬機(jī)(JVM)和Java標(biāo)準(zhǔn)庫(kù)。

  • 比如Python,其運(yùn)行時(shí)庫(kù)是指Python的解釋器 + Python標(biāo)準(zhǔn)庫(kù)。
  • 比如JavaScript,其運(yùn)行時(shí)庫(kù)是指瀏覽器 + JavaScript解釋器。

總的來(lái)說(shuō),語(yǔ)言的運(yùn)行時(shí)庫(kù),就是為該語(yǔ)言編寫(xiě)的程序,在運(yùn)行時(shí)提供基礎(chǔ)支持的庫(kù)或環(huán)境。

二、C/C++運(yùn)行時(shí)庫(kù)的功能

這一部分,我們來(lái)看看C/C++運(yùn)行時(shí)庫(kù)的具體功能。

對(duì)于 C 和 C++,其實(shí)是兩種不同的語(yǔ)言,后者是在前者的基礎(chǔ)上擴(kuò)展而來(lái),它們的運(yùn)行時(shí)庫(kù),也是有兩個(gè),我們分開(kāi)來(lái)看看。

1. C運(yùn)行時(shí)庫(kù)

C語(yǔ)言包括如下常用函數(shù):

  • 輸入輸出函數(shù),比如 printf、scanf、puts
  • 內(nèi)存管理函數(shù),比如 malloc、free、realloc
  • 文件系列函數(shù),比如 fopen、fread、fwrite
  • 字符串相關(guān)函數(shù),比如 strcpy、strcmp、strlen
  • 數(shù)學(xué)函數(shù),比如 sin、cos、sqrt

這些函數(shù)是怎么實(shí)現(xiàn)的?是由誰(shuí)提供的?

這些函數(shù)并不是由操作系統(tǒng)提供的,而是由C語(yǔ)言提供的,準(zhǔn)確說(shuō)是由C運(yùn)行時(shí)庫(kù)提供的。

比如內(nèi)存相關(guān):

Linux系統(tǒng)原本提供的內(nèi)存分配函數(shù)(也叫API),是brk、mmap等,而Windows系統(tǒng)提供的API是HeapAlloc、HeapFree、VirtualAlloc等,C語(yǔ)言(準(zhǔn)確說(shuō)是C運(yùn)行時(shí)庫(kù))把各操作系統(tǒng)提供的API封裝成統(tǒng)一的malloc、free。

如下圖,Linux下malloc調(diào)用系統(tǒng)API的堆棧:

Windows下malloc調(diào)用系統(tǒng)API的堆棧:

(事實(shí)上,malloc內(nèi)部并不是簡(jiǎn)單調(diào)用各系統(tǒng)API,而是做了一個(gè)內(nèi)存池,小內(nèi)存從池中分配,大內(nèi)存調(diào)系統(tǒng)API)

對(duì)于文件:

  • C語(yǔ)言提供的fopen、fread、fwrite等,即FILE系列的緩沖文件,同樣是調(diào)用各平臺(tái)的系統(tǒng)API實(shí)現(xiàn)的。Linux下是調(diào)用open、write等非緩沖文件接口,Windows下是調(diào)用CreateFile、WriteFile等。
  • 除了上面說(shuō)的平臺(tái)相關(guān)函數(shù)外,C運(yùn)行時(shí)庫(kù)里,還有一些是平臺(tái)無(wú)關(guān)或關(guān)系不大的函數(shù),比如字符串、數(shù)學(xué)相關(guān)函數(shù)等。
  • 另外C運(yùn)行時(shí)庫(kù)為了支持程序的運(yùn)行,還在統(tǒng)一的main函數(shù)前后做了一些邏輯,比如在main之前初始化一些全局變量、環(huán)境變量、命令行參數(shù)等,在main之后做一些資源的清理等。

總結(jié)一下,C運(yùn)行時(shí)庫(kù)里提供了一系列常用的庫(kù)函數(shù),包括把平臺(tái)相關(guān)函數(shù)封裝成統(tǒng)一的接口、平臺(tái)無(wú)關(guān)的,以及給我們提供了一個(gè)統(tǒng)一的main入口。

這些庫(kù)函數(shù)是事先編譯好的,通常隨編譯器一起發(fā)布,編譯器在編譯我們的程序時(shí),自動(dòng)幫我們做了鏈接,讓我們無(wú)感。

這也是為什么教科書(shū)說(shuō)C語(yǔ)言是可移植語(yǔ)言的原因之一,因?yàn)檫@些跨平臺(tái)實(shí)現(xiàn)幫我們屏蔽了操作系統(tǒng)的差異,否則就需要調(diào)各操作系統(tǒng)的API,為不同平臺(tái)編寫(xiě)不同的代碼了。

2. C++運(yùn)行時(shí)庫(kù)

C++語(yǔ)言,是在C語(yǔ)言的基礎(chǔ)上提供了更多的功能特性,包括:

  • 語(yǔ)言本身,提供了類、多態(tài)、new/delete、異常、RTTI等。
  • 標(biāo)準(zhǔn)庫(kù),提供了string、vector、list、map等各種容器和算法,以及輸入輸出流、智能指針、日期時(shí)間、線程相關(guān)等。

C++運(yùn)行時(shí)庫(kù),也是在C運(yùn)行時(shí)庫(kù)的基礎(chǔ)上,為C++語(yǔ)言的這兩部分特性提供支持。

這些庫(kù),語(yǔ)言提供者們幫我們實(shí)現(xiàn)好,放在一個(gè)動(dòng)態(tài)庫(kù)或靜態(tài)庫(kù)中,編譯器最終做鏈接即可。

3. 總結(jié)一下

C/C++運(yùn)行時(shí)庫(kù)的具體功能,綜合來(lái)看,有幾方面:

  • 支持程序的啟動(dòng)和退出。包括main之前的全局變量、環(huán)境變量、命令行參數(shù)的初始化,和main之后的資源清理等。
  • 把一些平臺(tái)相關(guān)API封裝成統(tǒng)一的庫(kù)函數(shù)或類,便于我們跨平臺(tái)開(kāi)發(fā)。
  • 其它常用功能的實(shí)現(xiàn),封裝成函數(shù)或類。
  • 少量語(yǔ)言特性的支持,比如異常處理、RTTI等。

三、各平臺(tái)的C/C++運(yùn)行時(shí)庫(kù)

前面介紹了C/C++運(yùn)行時(shí)庫(kù)的通用概念和主要功能,這部分介紹一下在各平臺(tái)的具體存在形式。

為了更好的理解,在之前先介紹一下語(yǔ)言的標(biāo)準(zhǔn)和實(shí)現(xiàn)、動(dòng)態(tài)庫(kù)與靜態(tài)庫(kù),以及各平臺(tái)的庫(kù)文件格式。

1. 標(biāo)準(zhǔn)與實(shí)現(xiàn)

C和C++語(yǔ)言,都是只有一種標(biāo)準(zhǔn),但有多種實(shí)現(xiàn)。

(1) 一種標(biāo)準(zhǔn)

即 ISO 的C/C++標(biāo)準(zhǔn)委員會(huì)制定的,但這個(gè)標(biāo)準(zhǔn)按時(shí)間順序,有多個(gè)版本,后一個(gè)版本在前一個(gè)版本基礎(chǔ)上改進(jìn),增加新的特性,同時(shí)也可能廢棄一些特性。

對(duì)于C語(yǔ)言,有C89/C90、C99、C11等;C++ 有 C++98、C++11、C++17、C++20 等。

C標(biāo)準(zhǔn)各版本簡(jiǎn)介:

  • C89:ANSI C,第一個(gè)版本,是美國(guó)標(biāo)準(zhǔn),有32個(gè)關(guān)鍵字,1989年
  • C90:和C89差不多,被國(guó)際ISO標(biāo)準(zhǔn)采納,1990年
  • C99:1999年發(fā)布,增加了多個(gè)特性,包括變長(zhǎng)數(shù)組、inline關(guān)鍵字、//注釋、宏可變參數(shù)
  • C11:2011年發(fā)布,增加了多線程、內(nèi)存對(duì)齊、Unicode等支持

C++標(biāo)準(zhǔn)各版本簡(jiǎn)介:

  • C++98:C++的第一個(gè)標(biāo)準(zhǔn)版本,1998年由國(guó)際標(biāo)準(zhǔn)化組織(ISO)發(fā)布。
  • C++03:對(duì)C++98的一個(gè)小修訂,2003年發(fā)布。
  • C++11:2011年發(fā)布,重大更新,引入了很多新特性,如auto類型、范圍for循環(huán)、列表初始化、lambda表達(dá)式等。
  • C++14:2014年發(fā)布,對(duì)C++11的一個(gè)小修訂。
  • C++17:2017年發(fā)布,重大更新,引入了很多新特性,如結(jié)構(gòu)化綁定、并行算法、模板參數(shù)自動(dòng)推導(dǎo)等。
  • C++20:2020年發(fā)布。引入了很多新特性,如概念、協(xié)程、模塊等。

語(yǔ)言的標(biāo)準(zhǔn),只是定義了語(yǔ)言的語(yǔ)法語(yǔ)義,以及有哪些頭文件、庫(kù)函數(shù)聲明等,但并不負(fù)責(zé)語(yǔ)言及這些庫(kù)函數(shù)的實(shí)現(xiàn)。

(2) 多種實(shí)現(xiàn)

語(yǔ)言的具體實(shí)現(xiàn),是由各編譯器廠商完成的,包括語(yǔ)法語(yǔ)義的實(shí)現(xiàn),和標(biāo)準(zhǔn)庫(kù)(運(yùn)行時(shí)庫(kù))的實(shí)現(xiàn)。

主要的有Linux下GNU的實(shí)現(xiàn),Windows下的MSVC實(shí)現(xiàn)、以及LLVM的實(shí)現(xiàn)等,后文會(huì)具體介紹。

標(biāo)準(zhǔn)和實(shí)現(xiàn)的關(guān)系:

標(biāo)準(zhǔn)和實(shí)現(xiàn)不一定是完全一致的。比如某編譯器版本,可能對(duì)標(biāo)準(zhǔn)某特性的遵循不夠完善,也有可能某個(gè)編譯器先實(shí)現(xiàn)了某語(yǔ)言特性,然后才被納入標(biāo)準(zhǔn)。

2. 靜態(tài)庫(kù)與動(dòng)態(tài)庫(kù)

與我們自己開(kāi)發(fā)的庫(kù)是分為靜態(tài)庫(kù)與動(dòng)態(tài)庫(kù)(也叫共享庫(kù))一樣,C/C++運(yùn)行時(shí)庫(kù)也是分為靜態(tài)庫(kù)與動(dòng)態(tài)庫(kù)。比如在Linux平臺(tái),靜態(tài)庫(kù)的后綴是.a,動(dòng)態(tài)庫(kù)的后綴是.so。

  • 動(dòng)態(tài)庫(kù)(共享庫(kù))會(huì)被多個(gè)程序共享,好處是程序體積小,但缺點(diǎn)是運(yùn)行時(shí)會(huì)多一些依賴。
  • 靜態(tài)庫(kù)正好相反,優(yōu)點(diǎn)是運(yùn)行時(shí)少依賴,但缺點(diǎn)是體積大,因?yàn)樗绘溄舆M(jìn)可執(zhí)行文件內(nèi)部。

這些靜態(tài)庫(kù)或動(dòng)態(tài)庫(kù),是由廠商開(kāi)發(fā),提前編譯好,然后在編譯器編譯鏈接我們的程序時(shí),自動(dòng)鏈接進(jìn)去。

3. 庫(kù)的文件格式

不同平臺(tái)的庫(kù)文件的二進(jìn)制格式是不一樣的,文件名后綴也不一樣,為了避免在后文中部分同學(xué)疑惑,這里簡(jiǎn)單介紹一下各平臺(tái)的庫(kù)文件格式。

可執(zhí)行/庫(kù)文件格式,常見(jiàn)的就是三種,我列成一個(gè)表格:

注:

  • ELF:Executable and Linkable Format
  • PE:Portable Executable
  • Mach-O:Mach Object file format

4. 各平臺(tái)的具體實(shí)現(xiàn)

現(xiàn)在我們開(kāi)始看看各平臺(tái)的具體實(shí)現(xiàn),包括Linux平臺(tái)的GNU實(shí)現(xiàn)、Windows的實(shí)現(xiàn)、LLVM的實(shí)現(xiàn)、移動(dòng)端的實(shí)現(xiàn),以及其它嵌入式平臺(tái)的實(shí)現(xiàn)。

(1) GNU的實(shí)現(xiàn)

這是Linux后臺(tái)開(kāi)發(fā)最常見(jiàn)的實(shí)現(xiàn),其C運(yùn)行時(shí)庫(kù)是GNU C Library,簡(jiǎn)稱glibc。

  • GNU簡(jiǎn)介:GNU是一個(gè)開(kāi)源項(xiàng)目,全稱GNU's Not Unix,目標(biāo)是開(kāi)發(fā)一個(gè)操作系統(tǒng),但由于其內(nèi)核開(kāi)發(fā)緩慢,我們實(shí)際上用到的是GNU的各種上層工具+Linux內(nèi)核的結(jié)合,即GNU/Linux。
  • GNU項(xiàng)目具體包括:GCC(多種編譯器集合)、C庫(kù)(即glibc)、bash及各種命令行工具、編輯器等。我們?nèi)粘=佑|到的Linux,其實(shí)準(zhǔn)確叫GNU/Linux,它是由GNU的各種上層工具+Linux內(nèi)核組成。常見(jiàn)的各種Linux發(fā)行版(Ubuntu、Debian、CentOS等),都屬于GNU/Linux。

在Linux平臺(tái),一個(gè)最簡(jiǎn)單的C語(yǔ)言程序,在運(yùn)行時(shí)會(huì)依賴哪些庫(kù)?

如下Hello World代碼:

// hello.c
#include <stdio.h>
int main() {
  printf("Hello World!\n");
  return 0;
}

使用 gcc 編譯,默認(rèn)動(dòng)態(tài)鏈接C運(yùn)行時(shí)庫(kù):

gcc hello.c -o hello

使用 ldd 命令查看依賴:

圖中的libc.so,即C的運(yùn)行時(shí)庫(kù)。

其實(shí)嚴(yán)格來(lái)說(shuō),圖中的這三個(gè)庫(kù),都是C程序的運(yùn)行時(shí)庫(kù),因?yàn)槎际菫槌绦虻倪\(yùn)行提供支持的。

順便看看文件大小:

Linux平臺(tái),一個(gè)最簡(jiǎn)單的C++程序,運(yùn)行時(shí)會(huì)依賴哪些庫(kù)?

如下Hello World代碼:

// hello.cpp
#include <iostream>
int main() {
    std::cout << "Hello World!" << std::endl;
    return 0;
}

使用g++編譯,默認(rèn)動(dòng)態(tài)鏈接C/C++運(yùn)行時(shí)庫(kù):

g++ hello.cpp -o hello

用 ldd 命令查看依賴:

圖中的libstdc++.so,即C++的運(yùn)行時(shí)庫(kù)。

附其它幾個(gè)庫(kù)簡(jiǎn)介:

  • linux-vdso.so:虛擬庫(kù),用于程序更高效的調(diào)用部分內(nèi)核接口
  • libm.so:數(shù)學(xué)庫(kù)
  • libgcc_s.so:gcc的支持庫(kù),用異常處理、RTTI等
  • ld-linux-x86-64.so:動(dòng)態(tài)庫(kù)的加載器

庫(kù)的靜態(tài)與動(dòng)態(tài):

gcc、g++對(duì)于C/C++運(yùn)行時(shí)庫(kù),默認(rèn)都是鏈接到其動(dòng)態(tài)庫(kù)版,但也可以鏈接到其靜態(tài)庫(kù)版。

方法是:

  • C庫(kù):gcc指定 -static 參數(shù),這樣就可以鏈接到靜態(tài)庫(kù) libc.a(事實(shí)上-static會(huì)把所有庫(kù)都靜態(tài)鏈接)
  • C++庫(kù):g++ 指定 -static-libstdc++ 參數(shù),這樣就可以鏈接到靜態(tài)庫(kù)版libstdc++.a

一個(gè)C程序使用 -static 靜態(tài)鏈接后,再看看其依賴的庫(kù):

看看文件大小:

圖中看出,二進(jìn)制不再依賴 libc.so 等庫(kù)了,這些庫(kù)被靜態(tài)鏈接到了可執(zhí)行文件內(nèi)部,體積也比之前大了。

(2) Windows的實(shí)現(xiàn)

在Windows平臺(tái),微軟也實(shí)現(xiàn)了自己的C/C++運(yùn)行時(shí)庫(kù),一般隨Visual Studio發(fā)行。

一個(gè)最簡(jiǎn)單的C/C++程序,使用vs2022編譯,默認(rèn)指定/MD選項(xiàng)

用 Depends 工具查看其依賴的動(dòng)態(tài)庫(kù):

圖中的紅框部分,就是C/C++運(yùn)行時(shí)庫(kù)。

在 Visual Studio 里,也可以設(shè)置動(dòng)態(tài)或靜態(tài)鏈接運(yùn)行時(shí)庫(kù),方法是:

工程設(shè)置里(C/C++ -> 代碼生成 -> 運(yùn)行庫(kù))

  • /MT 多線程,即鏈接到靜態(tài)庫(kù)版
  • /MD 多線程DLL,即鏈接到動(dòng)態(tài)庫(kù)版

另外還有個(gè) /MTd 、/MDd 是用于調(diào)試版本。

當(dāng)設(shè)置 /MT 即靜態(tài)鏈接后,就不再依賴這些庫(kù)了,如下圖:

hello.exe 文件體積從 13K 增加到 200K。

Windows平臺(tái)C/C++運(yùn)行時(shí)庫(kù)動(dòng)態(tài)版的文件名,不同的版本不一樣。

  • 早期的Vc6,是 msvcrt.dll 和 msvcp6.dll
  • 后來(lái)的版本,是 msvcrXX.dll 和 msvcpXX.dll(XX為版本號(hào))
  • 從vs2015開(kāi)始,微軟對(duì)其進(jìn)行了重構(gòu),拆分成了ucrtbase.dll、msvcp140.dll、vcruntime140.dll等

(3) LLVM的實(shí)現(xiàn)

LLVM是一個(gè)較新的開(kāi)源項(xiàng)目,可以說(shuō)是專門(mén)為做編譯器相關(guān)而生,是一個(gè)更優(yōu)的編譯器,各種配套的工具也比較完善,近年來(lái)越來(lái)越多的項(xiàng)目開(kāi)始使用LLVM。

LLVM里用于C系語(yǔ)言(C、C++ 和 Objective-C)的編譯器前端,叫Clang。

除了做編譯器外,LLVM也開(kāi)發(fā)了自己的C++庫(kù),名字叫 libc++ (不同于GNU的 libstdc++)。LLVM也有自己的C庫(kù),不過(guò)目前還不夠完善。

LLVM是一個(gè)跨平臺(tái)項(xiàng)目,支持多種平臺(tái),包括Linux、Windows、macOS、iOS、Android等。

比如在Linux平臺(tái),其C++運(yùn)行時(shí)庫(kù)的文件名,動(dòng)態(tài)庫(kù)版叫l(wèi)ibc++.so,靜態(tài)庫(kù)版叫l(wèi)ibc++.a。

(4) 移動(dòng)端的實(shí)現(xiàn)

① iOS:

在iOS及macOS平臺(tái),蘋(píng)果使用了LLVM的Clang作為Xcode內(nèi)置的C/C++編譯器。

  • 其C運(yùn)行時(shí)庫(kù)文件名是 libSystem.dylib,這個(gè)庫(kù)也包含了其它系統(tǒng)庫(kù)的功能。
  • 其C++運(yùn)行時(shí)庫(kù)是 libc++.dylib 或 libc++.a。

② Android:

Android平臺(tái)一般使用Java或Kotlin開(kāi)發(fā),但在某些性能要求高的場(chǎng)合,也會(huì)使用C/C++開(kāi)發(fā),即NDK開(kāi)發(fā)。

  • Android平臺(tái)的C運(yùn)行時(shí)庫(kù),叫Bionic libc,這是Google為Android專門(mén)開(kāi)發(fā)的,比glibc更輕量,更適合移動(dòng)設(shè)備。它同時(shí)提供了動(dòng)態(tài)庫(kù)版本(libc.so)和靜態(tài)庫(kù)版本(libc.a)。
  • Android NDK的C++運(yùn)行時(shí)庫(kù),以前支持三種:libc++(即LLVM的)、libstdc++(即GNU的)和STLport,后來(lái)從NDK r18開(kāi)始,只支持libc++了。

Android平臺(tái)里的 libc++ 庫(kù)的名字不太一樣,其動(dòng)態(tài)庫(kù)版叫 libc++_shared.so,靜態(tài)庫(kù)版本叫 libc++_static.a。

是鏈接到靜態(tài)庫(kù)還是動(dòng)態(tài)庫(kù),可以在工程里指定,比如:

APP_STL := c++_shared

(5) 其它實(shí)現(xiàn)

除了上面常見(jiàn)的實(shí)現(xiàn)之外,還有一些早期的C/C++編譯器,也都帶有自己的運(yùn)行時(shí)庫(kù),比如Turbo C(很多人在學(xué)校里用的)、Borland C++(有人用過(guò)嗎)、C++Builder 等。

在一些嵌入式平臺(tái),會(huì)使用一些更輕量的C運(yùn)行時(shí)庫(kù),主要有開(kāi)源的 Newlib、uClibc、musl 等。

5. 總結(jié)一下

總結(jié)一個(gè)表格:

四、C/C++運(yùn)行時(shí)庫(kù)相關(guān)問(wèn)題

在開(kāi)發(fā)中,我們常碰到的C/C++運(yùn)行時(shí)庫(kù)相關(guān)問(wèn)題,主要有多實(shí)例問(wèn)題和多版本問(wèn)題,下面分別介紹一下。

1. 運(yùn)行時(shí)庫(kù)的多實(shí)例問(wèn)題

為簡(jiǎn)單起見(jiàn),這里先只考慮單一開(kāi)發(fā)環(huán)境下(即只有一個(gè)編譯器和運(yùn)行時(shí)庫(kù)版本),進(jìn)程內(nèi)有多個(gè)運(yùn)行時(shí)庫(kù)實(shí)例的問(wèn)題。

先看一段代碼:

  • 動(dòng)態(tài)庫(kù)導(dǎo)出一個(gè)接口函數(shù):
char *GetData() {
  char *data = malloc(100);
  strcpy(data, "Hello World!");  // 僅演示,工作中不要用strcpy
  return data;
}
  • 主程序調(diào)用動(dòng)態(tài)庫(kù)的接口函數(shù):
int main() {
  char *data = GetData();
  free(data);
  return 0;
}

這段代碼,在有些平臺(tái)下運(yùn)行(主要是Windows平臺(tái)),可能會(huì)crash。

為什么會(huì)crash,根本原因是和運(yùn)行時(shí)庫(kù)的內(nèi)存堆有關(guān),下面具體講講。

(1) 單內(nèi)存堆

在C運(yùn)行時(shí)庫(kù)內(nèi)部,有一個(gè)內(nèi)存堆,也就是一個(gè)內(nèi)存池,如下圖:

圖片圖片

大多數(shù)情況下,進(jìn)程內(nèi)只有一個(gè)C運(yùn)行時(shí)庫(kù)實(shí)例,也就是只有一個(gè)內(nèi)存堆。

上述代碼的依賴關(guān)系:

App.exe
  A.dll
  crt.dll

注:用縮進(jìn)表示依賴關(guān)系,crt.dll 表示C運(yùn)行時(shí)庫(kù),相當(dāng)于linux的 libc.so

A.dll 向 crt.dll 申請(qǐng)內(nèi)存,用完后被 App.exe 再歸還給 crt.dll,這樣沒(méi)問(wèn)題。

但是當(dāng)進(jìn)程內(nèi)里有多個(gè)內(nèi)存堆時(shí),情況就不一樣了。

(2) 多內(nèi)存堆

當(dāng)進(jìn)程內(nèi)有多個(gè)C運(yùn)行時(shí)庫(kù)實(shí)例時(shí),就會(huì)有多個(gè)內(nèi)存堆實(shí)例。

比如在Windows平臺(tái),A.dll 設(shè)置 /MT 選項(xiàng),即靜態(tài)鏈接C/C++運(yùn)行時(shí)庫(kù),主程序默認(rèn)/MD選項(xiàng),即動(dòng)態(tài)鏈接C/C++運(yùn)行時(shí)庫(kù)。

這時(shí)的依賴關(guān)系:

App.exe
  A.dll (靜態(tài)鏈接crt)
  crt.dll

這種場(chǎng)景下,A.dll 會(huì)從自己的內(nèi)存堆中分配內(nèi)存,用完后被 App.exe 歸還給了 crt.dll 的內(nèi)存堆,這樣就引起了內(nèi)存堆的結(jié)構(gòu)異常,出現(xiàn)crash。

(3) 隱藏更深的情況

下面再看一種隱藏更深的情況,工作中更為常見(jiàn)。

動(dòng)態(tài)庫(kù) A 導(dǎo)出一個(gè)接口:

void GetData(std::string &data) {
  data = "Hello World!";  // 這句賦值內(nèi)部,string會(huì)分配內(nèi)存
}

在主程序或另一個(gè)動(dòng)態(tài)庫(kù)中,調(diào)用動(dòng)態(tài)庫(kù) A 的接口:

void Test() {
  std::string data;
  GetData(data);
  // data析構(gòu),釋放string內(nèi)部之前分配的內(nèi)存,導(dǎo)致crash
}

上述代碼在靜態(tài)鏈接C/C++運(yùn)行時(shí)庫(kù)時(shí),會(huì)出現(xiàn)crash。

這里crash的本質(zhì),其實(shí)和前面一樣的,即一個(gè)模塊的分配的內(nèi)存,交給了另一個(gè)模塊釋放。

除此之外,這個(gè)代碼,還有另一個(gè)風(fēng)險(xiǎn),即如果兩個(gè)模塊使用了不同的編譯器或C++庫(kù)(比如一個(gè)使用GCC編譯,一個(gè)使用LLVM編譯),就可能會(huì)出現(xiàn)string的內(nèi)存結(jié)構(gòu)不一致而異常。

(4) 其它情況

在進(jìn)程內(nèi)有多C/C++運(yùn)行時(shí)庫(kù)實(shí)例時(shí),還有一些其它有問(wèn)題的情況,比如跨模塊傳遞文件指針、跨模塊傳遞環(huán)境變量等。

比如:

// 動(dòng)態(tài)庫(kù)導(dǎo)出該接口
void WriteData(FILE *file) {
  fwrite(...);
  fclose(file);
}

// 主程序里
int main() {
  FILE *file = fopen(...);
  WriteData(file);
  return 0;
}

(5) 總結(jié)一下

運(yùn)行時(shí)庫(kù)的多實(shí)例,是由靜態(tài)鏈接C/C++運(yùn)行時(shí)庫(kù)引起。在這種多實(shí)例場(chǎng)景下,一些不太好的代碼寫(xiě)法,就會(huì)表現(xiàn)出問(wèn)題。

如何避免這些問(wèn)題,建議:

① 作為動(dòng)態(tài)庫(kù)的設(shè)計(jì)者:

  • 盡量做到內(nèi)存「誰(shuí)分配誰(shuí)釋放」的原則
  • 盡量避免庫(kù)間接口傳遞C/C++對(duì)象

這樣將會(huì)有更好的兼容性,即使在進(jìn)程內(nèi)有多個(gè)C/C++運(yùn)行時(shí)庫(kù)時(shí),也不會(huì)有問(wèn)題。

② 作為App的總體設(shè)計(jì)者:

盡量保證進(jìn)程內(nèi)只有一份C/C++運(yùn)行時(shí)庫(kù)實(shí)例

這樣也是會(huì)有更好的兼容性,能避免很多潛在問(wèn)題。

如果用生活中的例子來(lái)類比,多運(yùn)行時(shí)庫(kù),就相當(dāng)于一個(gè)公司對(duì)接了多個(gè)銀行,某個(gè)部門(mén)從 A 銀行借來(lái)的錢(qián),被另一個(gè)部門(mén)還給了 B 銀行,這樣就引起了問(wèn)題。

解決方法就是誰(shuí)借的錢(qián),誰(shuí)來(lái)還,另外就是一個(gè)公司盡量只對(duì)接一個(gè)銀行。

關(guān)于運(yùn)行時(shí)庫(kù)的多實(shí)例,幸運(yùn)的是,大部分平臺(tái)的編譯器,已經(jīng)幫我們規(guī)避了可能會(huì)導(dǎo)致運(yùn)行時(shí)庫(kù)多實(shí)例的問(wèn)題。

我簡(jiǎn)單測(cè)了一下:

  • Linux平臺(tái):對(duì)于主程序和動(dòng)態(tài)庫(kù),都默認(rèn)動(dòng)態(tài)鏈接C/C++運(yùn)行時(shí)庫(kù),主程序允許靜態(tài)鏈接C/C++運(yùn)行時(shí)庫(kù)(方法是 -static 選項(xiàng)),動(dòng)態(tài)庫(kù)不支持靜態(tài)鏈接C/C++運(yùn)行時(shí)庫(kù)。
  • Windows平臺(tái):對(duì)于主程序和動(dòng)態(tài)庫(kù),都支持動(dòng)態(tài)或靜態(tài)鏈接C/C++運(yùn)行時(shí)庫(kù)。
  • iOS和macOS平臺(tái):不支持靜態(tài)鏈接C/C++運(yùn)行時(shí)庫(kù)。
  • Android平臺(tái):動(dòng)態(tài)庫(kù)支持動(dòng)態(tài)或靜態(tài)鏈接C++運(yùn)行時(shí)庫(kù),默認(rèn)靜態(tài)鏈接,這可能導(dǎo)致風(fēng)險(xiǎn),官方文檔提供了說(shuō)明。

所以總結(jié)來(lái)看,這個(gè)問(wèn)題主要是在Windows和Android平臺(tái)可能會(huì)出現(xiàn),其它平臺(tái)編譯器不提供這種設(shè)置(特殊方法除外,比如自己開(kāi)發(fā)一個(gè)運(yùn)行時(shí)庫(kù)并靜態(tài)鏈接)。

如果你是做Windows和Android平臺(tái)開(kāi)發(fā),或者做跨平臺(tái)開(kāi)發(fā),涉及到這兩個(gè)平臺(tái),就必須考慮運(yùn)行時(shí)庫(kù)多實(shí)例問(wèn)題,否則就可以不用太操心這個(gè)問(wèn)題,編譯器已經(jīng)防止了你犯錯(cuò)。

2. 運(yùn)行時(shí)庫(kù)的多版本問(wèn)題

最后這一部分,簡(jiǎn)單說(shuō)說(shuō)運(yùn)行時(shí)庫(kù)的多版本問(wèn)題。

和我們自己開(kāi)發(fā)的軟件或庫(kù),會(huì)不斷升級(jí),會(huì)有多個(gè)版本一樣,C/C++運(yùn)行時(shí)庫(kù),也是在不斷升級(jí),有很多個(gè)版本。

運(yùn)行時(shí)庫(kù)的多版本,會(huì)引起兩個(gè)問(wèn)題:

  • 編譯時(shí)用的庫(kù),和運(yùn)行時(shí)用的庫(kù),版本不一致
  • 編譯同一個(gè)App的多個(gè)部分,用的庫(kù)版本不一致

后者在開(kāi)發(fā)一些大型項(xiàng)目,不同模塊分屬不同團(tuán)隊(duì)開(kāi)發(fā)時(shí),更容易出現(xiàn)。

這種不一致,可能會(huì)引起不匹配,從而產(chǎn)生各種問(wèn)題。

(1) 問(wèn)題現(xiàn)象

有些是在鏈接時(shí)不通過(guò),提示符號(hào)沖突或找不到。比如:

undefined reference to `std::string::operator=(std::string const&)'
undefined reference to `std::ostream::operator<<(std::ostream& (*)(std::ostream&))'

(也有可能是設(shè)置不統(tǒng)一引起)

有些是在啟動(dòng)時(shí),動(dòng)態(tài)庫(kù)加載器檢測(cè)出異常,提示缺少符號(hào),比如:

./test: /lib64/libstdc++.so.6: version `CXXABI_1.3.9' not found (required by ./test)
./test: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by ./test)
./test: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.22' not found (required by ./test)

有些是啟動(dòng)時(shí)沒(méi)檢測(cè)出異常,而在運(yùn)行時(shí)會(huì)有莫名其妙的問(wèn)題。

(2) 解決方法

解決的方法,無(wú)它,只能是盡量保證一致,包括:

  • 編譯各模塊時(shí),用的開(kāi)發(fā)環(huán)境(包括編譯器版本+運(yùn)行時(shí)庫(kù)版本+參數(shù)設(shè)置)一致。
  • 運(yùn)行時(shí)和編譯時(shí)的庫(kù)環(huán)境一致。

具體怎么保持一致,有如下一些方法:

① 靜態(tài)鏈接

即不再依賴環(huán)境中的C/C++運(yùn)行時(shí)庫(kù),用體積換取少依賴。這個(gè)在Windows平臺(tái)較常用,Android平臺(tái)在只有一個(gè)動(dòng)態(tài)庫(kù)時(shí),官方也推薦靜態(tài)鏈接。

② 開(kāi)發(fā)環(huán)境多版本共存時(shí)

選擇其中一個(gè)編譯器和C/C++運(yùn)行時(shí)庫(kù)版本。比如在Windows平臺(tái)選擇工具集(v142/v143等),Linux平臺(tái)通過(guò)環(huán)境變量選擇GCC版本、Android平臺(tái)環(huán)境變量選擇NDK版本(r23/r25等)。

③ 運(yùn)行環(huán)境多版本共存時(shí)

通過(guò)指定搜索路徑選擇其中一個(gè)版本。比如Linux平臺(tái)在二進(jìn)制中指定(rpath)、環(huán)境變量指定(LD_LIBRARY_PATH)。

④ Docker

Linux平臺(tái)常用,即運(yùn)行時(shí)提供一個(gè)隔離的干凈的環(huán)境。

五、總結(jié)

本文介紹了什么是C/C++運(yùn)行時(shí)庫(kù),運(yùn)行時(shí)庫(kù)的主要的功能,各平臺(tái)的存在形式,以及開(kāi)發(fā)要注意的問(wèn)題,包括多實(shí)例問(wèn)題和多版本問(wèn)題等。遵守一些開(kāi)發(fā)原則,以及保證運(yùn)行時(shí)庫(kù)單實(shí)例、開(kāi)發(fā)和運(yùn)行環(huán)境的一致,可以避免很多潛在問(wèn)題。

責(zé)任編輯:趙寧寧 來(lái)源: 騰訊技術(shù)工程
相關(guān)推薦

2010-01-27 14:14:48

C++程序運(yùn)行時(shí)間

2011-08-19 15:05:29

異常處理

2023-11-21 16:31:51

C++語(yǔ)言

2024-12-09 13:00:00

C++類型安全

2011-12-27 09:39:12

C#運(yùn)行時(shí)

2011-07-10 15:36:54

C++

2010-01-15 10:41:06

CC++

2015-07-20 15:44:46

Swift框架MJExtension反射

2014-09-02 10:39:53

Go語(yǔ)言C語(yǔ)言

2010-02-02 11:16:28

C++異常

2024-11-27 08:26:00

C++模板靜態(tài)

2011-05-18 17:33:15

CC++

2024-03-21 09:15:58

JS運(yùn)行的JavaScrip

2023-12-18 11:15:03

2010-02-06 09:53:26

C++ void

2010-02-01 16:13:15

C++繼承

2019-07-12 09:30:12

DashboardDockerDNS

2021-09-11 15:38:23

容器運(yùn)行鏡像開(kāi)放

2009-02-10 09:03:59

動(dòng)態(tài)語(yǔ)言CLRVB.NET

2023-11-28 11:51:01

C++函數(shù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 婷婷不卡 | 精品网 | 视频一区中文字幕 | 午夜天堂 | 操人网站 | 美国a级毛片免费视频 | 成人av电影免费在线观看 | 久久久久久久久久爱 | 黄页网址在线观看 | 国产精品黄色 | 中文字幕三区 | 日本一区高清 | 国产一区二区三区视频免费观看 | 成人久久网 | 成人一区二区电影 | 伊人春色成人 | 日韩国产一区二区 | 欧美a在线 | 国产一区不卡 | 欧美久久久 | 免费观看黄色一级片 | 日韩中文字幕一区二区 | 欧美日韩一区二区在线 | 中文成人无字幕乱码精品 | 国产原创视频 | 精品九九九 | 一区视频在线免费观看 | 亚洲视频精品 | 很很干很很日 | 亚洲成a人片 | 日韩欧美黄色 | 中文字幕在线观看第一页 | 综合色在线 | 久久久久久久久国产 | 天天爽夜夜爽精品视频婷婷 | 精品在线视频播放 | 国产一区二区免费电影 | 一区二区三区在线观看免费视频 | 精品久久久久一区二区国产 | 精品日韩欧美一区二区 | 精品国产乱码久久久久久影片 |