一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

深入探索Linux中的C語言

Linux閱碼場 ? 來源:Linux閱碼場 ? 2023-03-14 16:48 ? 次閱讀

本章將深入探索 Linux 中的 C 語言。在本章中,我們將學(xué)到更多關(guān)于編譯器、從源碼到二進(jìn)制程序的 4 個步驟、如何使用 Make 工具以及系統(tǒng)調(diào)用和 C 標(biāo)準(zhǔn)庫函數(shù)的差別的知識。我們也將學(xué)習(xí)一些 Linux 中的基礎(chǔ)頭文件、C 語言標(biāo)準(zhǔn)以及可移植操作系統(tǒng)(POSIX)標(biāo)準(zhǔn),C 語言是和 Linux 緊密結(jié)合的,掌握 C 語言可以幫你更好地學(xué)習(xí) Linux。

本章中,我們將會學(xué)習(xí)如何開發(fā) Linux C 語言程序和庫,學(xué)習(xí)如何編寫通用的Makefile 以及為一些重要的項(xiàng)目編寫高級的 Makefile。在學(xué)習(xí)的過程中,我們也會學(xué)到各種 C 語言標(biāo)準(zhǔn)、它們的區(qū)別以及對程序有哪些影響。

本章涵蓋以下主題:

使用 GNU 編譯器套件(GCC)鏈接庫

切換 C 標(biāo)準(zhǔn)

使用系統(tǒng)調(diào)用

何時(shí)不使用它們

獲取 Linux 和類 UNIX 頭文件信息

定義功能測試宏

編譯過程的 4 個步驟

使用 Make 編譯

使用 GCC 選項(xiàng)編寫一個通用的 Makefile

編寫一個簡單的 Makefile

編寫一個更高級的 Makefile

3.1技術(shù)要求

在開始學(xué)習(xí)之前,你需要 GCC 編譯器、Make 工具。

本章中的所有代碼示例都可以從 GitHub 下載:

https://github.com/PacktPublishing/Linux-System-Programming-Techniques/tree/master/ch3

3.2使用 GNU 編譯器套件鏈接庫

本節(jié)中,你將會學(xué)到如何把程序鏈接到一個外部庫—一個安裝在系統(tǒng)層面,另一個安裝在主目錄中。在鏈接庫之前,我們需要創(chuàng)建它,這也是本節(jié)中我們要學(xué)習(xí)的內(nèi)容。學(xué)習(xí)如何鏈接庫可以讓你復(fù)用庫提供的大量現(xiàn)成函數(shù),無須編寫所有內(nèi)容,就可以使用庫文件已提供的功能。通常來說,沒有必要重新發(fā)明輪子,這可以節(jié)約大量的時(shí)間。

3.2.1準(zhǔn)備工作

在本范例中,你只需要用到 3.1 節(jié)中列出的工具。

3.2.2實(shí)踐步驟

我們開始學(xué)習(xí)如何鏈接系統(tǒng)中的共享庫以及主目錄中的庫。我們從已有的標(biāo)準(zhǔn)庫開始:math 庫。

3.2.2.1鏈接到 math 庫我們會編寫一個計(jì)算銀行賬戶復(fù)利的小程序,會用到 math 庫中的 pow() 函數(shù):1. 把下面的代碼寫入文件,并將其命名為 interest.c。注意,文件頂部包含 math.h,pow() 函數(shù)的第一個參數(shù)是基數(shù),第二個參數(shù)是指數(shù):

#include 
#include 
int main(void)
{
 int years = 15; /* The number of years you will 
 * keep the money in the bank 
 * account */
 int savings = 99000; /* The inital amount */
 float interest = 1.5; /* The interest in % */
 printf("The total savings after %d years " 
 "is %.2f
", years, 
 savings * pow(1+(interest/100), years));
 return 0;
}

2. 編譯并鏈接程序。鏈接庫的選項(xiàng)是 -l,庫的名稱是 m (更多信息請參閱 man 3 pow 手冊頁:

$>gccinterest.c-ointerest-lm

3.運(yùn)行程序:

$>./interest

The total savingsafter 15 years is 123772.95

3.2.2.2 創(chuàng)建自己的庫

我們開始學(xué)習(xí)創(chuàng)建自己的共享庫。下一節(jié),我們會鏈接一個進(jìn)程到這個庫。這個庫的 作用是確定一個數(shù)是否為素?cái)?shù)。

1. 我們從創(chuàng)建一個簡單的頭文件開始,這個文件只包含一行:函數(shù)原型。把以下內(nèi)容 寫入文件并將其命名為 prime.h:

int isprime(long int number);

2. 現(xiàn)在開始編寫庫文件中的實(shí)際函數(shù),把以下代碼寫入文件并將其命名為 prime.c:

int isprime(long int number)
{
long int j;
int prime = 1;
/* Test if the number is divisible, starting
* from 2 */
for(j=2; j

我們需要通過一些手段把其轉(zhuǎn)換成庫。第一步是把它編譯成一個叫目標(biāo)文件的對象。我們還需要向編譯器傳遞一些額外的參數(shù)使其作為庫運(yùn)行。更具體一些,我們需要 使其成為位置無關(guān)的代碼 ( PIC)。運(yùn)行以下編譯命令會生成一個叫prime.o 的文 件,使用 ls -l 命令來查看文件,我們將在本章后面學(xué)到更多關(guān)于目標(biāo)文件的知識:

$>gcc-Wall-Wextra-pedantic-fPIC -c prime.c

$>ls -lprime.o

-rw-r--r-- 1 jakejake 1296 nov 28 19:18prime.o

4. 現(xiàn)在,我們將目標(biāo)文件打包成一個庫,在下面的命令中, -shared 參數(shù)創(chuàng)建一個共 享庫。-Wl、-soname、libprime .so 參數(shù)用于鏈接器。它告訴鏈接器這個共享 庫的名字( soname)叫 libprime .so。-o 參數(shù)指定輸出文件名,即 libprime . so。這是動態(tài)鏈接庫的標(biāo)準(zhǔn)命名約定,結(jié)尾的so 代表共享對象。當(dāng)庫要在系統(tǒng)范 圍內(nèi)使用時(shí),通常會添加一個數(shù)字來指示版本。在命令的最后,我們加上需要被包 含在共享庫中的目標(biāo)文件 prime .o:

$>gcc-shared-Wl,-soname,libprime.so-o

>libprime.soprime.o

3.2.2.3 鏈接到主目錄中的庫

有時(shí),你想要鏈接到主目錄(或其他目錄)中的共享庫。它可能是你從網(wǎng)上下載的庫或 者你自己構(gòu)建的庫。我們將會在本書的后續(xù)章節(jié)學(xué)習(xí)更多創(chuàng)建共享庫的知識。這里,我們 使用剛剛創(chuàng)建的 libprime.so 共享庫。

1. 把以下代碼寫入文件并將其命名為is-it-a-prime .c。這個程序?qū)褂玫絼偛?創(chuàng)建的共享庫。程序代碼中必須包含剛剛創(chuàng)建的頭文件 prime .h。請注意包含本地 頭文件的不同語法(不是系統(tǒng)級別的頭文件):

#include 
#include 
#include 
#include "prime.h"
int main(int argc, char*argv[])
{
long int num;
/* Only one argument is accepted
*/
if (argc != 2)
{
fprintf(stderr,
"Usage: %s number
",
argv[0]);
return 1;
}
/* Only numbers0-9 are accepted */
if (strspn(argv[1], "0123456789") !=
strlen(argv[1]) )
{
fprintf(stderr, "Only numeric values are"
"accepted
");
return 1;
}
num =atol(argv[1]); /* String to long */
if (isprime(num))
/* Check if num is a prime */
{
printf("%ld
is a prime
", num);
}
else
{
printf("%ld
is not a prime
", num);
}
return 0;
}

2. 編譯并把它鏈接到 libprime .so。由于共享庫在主目錄中,因此需要指定共享庫 的路徑:

$>gcc-L${PWD}is-it-a-prime.c

>-o is-it-a-prime-lprime

3.在運(yùn)行程序之前,我們需要設(shè)置環(huán)境變量 $LD_LIBRARY_PATH 為當(dāng)前目錄(也就

是共享庫所在的目錄)。原因是這個庫是動態(tài)鏈接的,它并不在系統(tǒng)庫所在的路徑:

$> export LD_LIBRARY_PATH=${PWD}:${LD_LIBRARY_PATH}

4.運(yùn)行程序。用一些不同的數(shù)字測試一下,看看它們是否為素?cái)?shù):

$>./is-it-a-prime11

11 isaprime

$>./is-it-a-prime13

13 isaprime

$>./is-it-a-prime15

15 is not a prime

$>./is-it-a-prime1000024073

1000024073 is a prime

$>./is-it-a-prime1000024075

1000024075 is not a prime

我們可以通過 ldd 命令查看程序依賴哪些共享庫,如果我們檢查is-it-a-prime 程序,會看到它依賴 libprime .so 庫。當(dāng)然它也還有其他依賴項(xiàng),例如 libc.so.6,這是標(biāo)準(zhǔn)的 C 庫:

$>ldd is-it-a-prime

linux-vdso.so.1(0x00007ffc3c9f2000)

libprime.so => /home/jake/libprime.so

(0x00007fd8b1e48000)

libc.so.6=> /lib/x86_64-linux-gnu/libc.so.6

(0x00007fd8b1c4c000)

/lib64/ld-linux-x86-64.so.2 (0x00007fd8b1e54000)

3.2.3 它是如何工作的

在“鏈接到 math 庫”一節(jié)中使用的 pow() 函數(shù)需要鏈接標(biāo)準(zhǔn)庫中的 math 庫 libm . so。你可以在系統(tǒng)庫路徑中找到這個庫文件,它通常位于 /usr/lib 或 /usr/lib64。對于 Debian 和 Ubuntu 發(fā)行版來說,它通常位于 /usr/lib/x86_64-linux-gnu (對于 64 位系統(tǒng)來說)。由于這個文件位于系統(tǒng)默認(rèn)的庫文件路徑,因此我們可以僅使用 -l 參數(shù) 來包含它。math 庫文件的全稱是 libm .so,但是當(dāng)我們指定庫文件時(shí),只寫了 m (即我們刪除了 lib 和 .so 的擴(kuò)展名), -l 和 m 之間不應(yīng)該有空格,所以鏈接時(shí),使用-lm。

我們需要鏈接到 math庫才能使用 pow() 函數(shù)的原因是 math庫和標(biāo)準(zhǔn) C庫 libc .so 是分開的。我們之前使用的函數(shù)都是標(biāo)準(zhǔn)庫 libc .so 提供的,這個庫默認(rèn)就被鏈接,所 以不需要指定,如果想在編譯時(shí)顯示指定libc .so 的鏈接,可以執(zhí)行 gcc -lc some- program.c -o some-program。

pow() 函數(shù)接受 2 個參數(shù):x 和 y,例如 pow(x,y)。函數(shù)返回 x 的 y 次冪值。比如 pow(2,8)返回 256。返回值類型和參數(shù) x、y 的類型都是 double 浮點(diǎn)數(shù)。

計(jì)算復(fù)利的公式如下所示:

21d3b2f8-c23a-11ed-bfe3-dac502259ad0.png

這里,P是你存入賬戶的起始資本, r 是利率(以百分比表示),y 是資金存在銀行的年數(shù)。

鏈接到主目錄中的庫

在 C 程序 is-it-a-prime .c 中,我們需要包含 prime.h 頭文件。頭文件只有一行 isprime() 的函數(shù)原型。實(shí)際的 isprime()函數(shù)在 prime.o 創(chuàng)建的 libprime.so 庫 文件中。 .so 文件叫作共享庫或共享對象文件。共享庫包含已編譯的函數(shù)目標(biāo)文件。我們 將在本章后面介紹什么是目標(biāo)文件。

當(dāng)我們鏈接到自己下載或者創(chuàng)建的共享庫時(shí),會比鏈接系統(tǒng)庫麻煩一點(diǎn),因?yàn)樗鼈儧] 有安裝在系統(tǒng)庫的默認(rèn)路徑。

首先,我們需要指定共享庫的名字和路徑,路徑通過-L 參數(shù)指定。本章的例子中, 我們指定路徑為當(dāng)前目錄,也就是我們創(chuàng)建庫文件的地方。我們通過${PWD} 來指定當(dāng) 前目錄, ${PWD} 是一個 shell 環(huán)境變量,它表示當(dāng)前目錄的絕對路徑。你可以嘗試執(zhí)行 echo ${PWD} 看看輸出。

現(xiàn)在還不能運(yùn)行程序, 我們還需要設(shè)置另外一個環(huán)境變量 $LD_LIBRARY_PATH,把 $LD_LIBRARY_PATH 設(shè)置為當(dāng)前目錄(同時(shí)必須包含變量中已有的路徑)。原因是這是 一個動態(tài)鏈接庫,庫文件并不包含在程序中,意味著程序運(yùn)行時(shí)需要找到共享庫。環(huán)境變 量 $LD_LIBRARY_PATH 的作用,就是告訴程序到哪里找這個庫文件。同時(shí)我們也不想覆 蓋 $LD_LIBRARY_PATH 已有的內(nèi)容,因此設(shè)置時(shí)需要包含原有內(nèi)容。如果沒有設(shè)置這 個環(huán)境變量,在執(zhí)行程序時(shí)會收到一條錯誤消息,即“ error while loading shared libraries:libprime.so ”。當(dāng)我們使用 ldd 查看程序依賴時(shí),可以看到 libprime .so 位于主目錄中, 而不是系統(tǒng)的路徑。

3.2.4更多

如果你對標(biāo)準(zhǔn) C 庫有興趣,可以閱讀 libc 的 man 手冊。想了解 pow() 函數(shù),可以 閱讀 man 3 pow。

我也鼓勵你通過 man ldd 閱讀 ldd 的手冊,并使用 ldd 查看進(jìn)程的依賴,比如在本 節(jié)中編寫的interest 進(jìn)程。你會看到 libm .so 庫及其系統(tǒng)路徑。你也可以嘗試用 ldd查看系統(tǒng)二進(jìn)制,比如 /bin/ls。

3.3 切換 C標(biāo)準(zhǔn)

在本范例中,我們會學(xué)習(xí)不同的 C 標(biāo)準(zhǔn),它們是什么、為什么重要,以及它們?nèi)绾斡?響程序。我們還會學(xué)習(xí)如何在編譯時(shí)選擇 C 標(biāo)準(zhǔn)。

現(xiàn)在幾種最通用的 C 標(biāo)準(zhǔn)是 C89 、C99 和 C11 ( C89 是 1989 年發(fā)布的,C11 是 2011 年發(fā)布的,以此類推)。很多編譯器仍然默認(rèn)使用 C89 標(biāo)準(zhǔn),因?yàn)樗羌嫒菪宰詈?,使?最廣泛,實(shí)現(xiàn)最完整的。不過,C99 是一種更加靈活和更加現(xiàn)代化的實(shí)現(xiàn)。通常在較新的 Linux 版本里,默認(rèn)使用 C18 標(biāo)準(zhǔn)以及一些 POSIX 標(biāo)準(zhǔn)。

在本范例中,我們會編寫 2個進(jìn)程,并分別用 C89 和 C99 編譯, 看看它們的區(qū)別。

3.3.1 準(zhǔn)備工作

在本范例中你只需要一臺安裝有 Linux 系統(tǒng)的計(jì)算機(jī),并且安裝 GCC,最好通過我們 在第 1 章中介紹的軟件包來安裝。

3.3.2 實(shí)踐步驟

我們繼續(xù)探索不同 C 標(biāo)準(zhǔn)的差異。

1. 把下面的代碼寫入文件,并將其命名為no-return.c。注意,代碼中缺少 return 語句:

#include 
int main(void)
{
printf("Hello, world
");
}

2.用 C89 標(biāo)準(zhǔn)編譯程序:

$> gcc -std=c89 no-return.c -o no-return

3.運(yùn)行程序并檢查退出碼:

$> ./no-return
Hello, world
$> echo $?
13

4. 仍然使用 C89 標(biāo)準(zhǔn)編譯程序,但是開啟所有類型警告、擴(kuò)展語法警告,以及pedantic 檢查( -W 表示警告參數(shù), all 表示警告類型,所以用 -Wall),注意 GCC 輸出的錯

誤消息:

$> gcc -Wall -Wextra -pedantic -std=c89 
> no-return.c -o no-return
no-return.c:
In function 'main':
no-return .c:6:1: warning: control reaches end of non-void
function [-Wreturn-type]
}

5. 改用 C99 標(biāo)準(zhǔn)重新編譯程序,并開啟所有類型警告和pedantic 檢查?,F(xiàn)在就不會顯 示錯誤:

$>gcc-Wall-Wextra-pedantic -std=c99

>no-return.c-ono-return

6.重新運(yùn)行程序,并檢查退出碼??纯春椭暗膮^(qū)別。

$>./no-return

Hello, world

$>echo$?

0

7. 把下面的代碼寫入文件,并將其命名為 for-test .c。這個程序在 for 循環(huán)內(nèi)新定 義了一個 i 整型變量,只有 C99 允許這個寫法:

#include 
int main(void)
{
for (int i = 10;
i>0; i--)
{
printf("%d
",
i);
}
return 0;
}

8. 用 C99 標(biāo)準(zhǔn)編譯:

$>gcc-std=c99for-test.c-ofor-test

9.運(yùn)行這個程序,可以看到它正常工作:

$> ./for-test
10
9
8
7
6
5
4
3
2
1

10. 現(xiàn)在嘗試用 C89 標(biāo)準(zhǔn)編譯。注意 GCC 的報(bào)錯明確說明了這個用法只在 C99 或更高 版本被允許。GCC 的報(bào)錯都很有用,所以一定要認(rèn)真看,它可以幫你節(jié)約大量時(shí)間。

$> gcc -std=c89 for-test.c -o for-test
for-test.c: In function 'main':
for-test .c:5:5: error: 'for' loop initialdeclarations
are only allowed in C99 or C11 mode
for (int i = 10;
i>0; i--)

11. 現(xiàn)在編寫下面的小程序并將其命名為 comments .c。這個程序使用了 C99 注釋(也 稱為 C++ 注釋):

#include 
int main(void)
{
// A C99 comment
printf("hello, world
");
return 0;
}

12.用 C99 編譯程序:

$>gcc -std=c99comments.c-ocomments

13.現(xiàn)在嘗試用 C89 標(biāo)準(zhǔn)編譯程序。注意這里 GCC 的報(bào)錯也很有用:

$> gcc -std=c89 comments.c -o comments
comments.c: In function 'main':
comments .c:5:5: error:C++ style comments are not allowed
in ISO C90
// A C99 comment
^
comments .c:5:5: error:
(this will be reported only once
per input file)

3.3.3 它是如何工作的

這只是 C89 和 C99 的一些常見區(qū)別,在 Linux 上使用 GCC 還有其他不明顯的差異。我們在 3.3.4節(jié)會討論其中的一些不可見差異。

我們通過 GCC 的 -std 參數(shù)選擇不同的 C 標(biāo)準(zhǔn)。在本節(jié)中,我們測試了2個標(biāo)準(zhǔn),C89 和 C99。

在第 1~ 6步中,我們看到函數(shù)忘記返回值這種情況在編譯時(shí)的區(qū)別。在C99 中, 由 于未指定其他值,因此假定返回值為 0。但是在 C89 中,忘記返回值是不行的。程序可以 編譯通過,但是程序運(yùn)行時(shí)會返回 13(錯誤碼),這是錯誤的,因?yàn)槌绦蛑袥]有發(fā)生錯誤。你測試時(shí)返回的實(shí)際值可能不同,但錯誤碼始終大于0。當(dāng)編譯時(shí)啟用所有警告、額外警告 和 pedantic 檢查( -Wall -Wextra -pedantic)時(shí),可以看到警告輸出這意味著忘記返 回值是不合法的。所以,在C89 中總是返回一個帶有 return 的值。

在第 7 ~ 10步中,我們看到 C99 可以在 for 循環(huán)中聲明一個新變量,這在 C89 中是 不可以的。

在第 11~ 13步中,我們看到一種新的注釋方式:2個斜杠 //。這在 C89 中也是不合法的。

3.3.4更多

除了 C89 和 C99,還有很多的 C 語言標(biāo)準(zhǔn)和方言。例如 C11 、GNU99 ( GNU 的 C99 方言)、 GNU11 ( GNU 的 C11 方言)等,但今天最常用的 C 語言標(biāo)準(zhǔn)是 C89 、C99 和 C11。C18 開始作為某些編譯器和發(fā)行版的默認(rèn)設(shè)置。

實(shí)際上,C89 和 C99 的差異比我們在這里介紹的更多, 其中一些差異無法使用 Linux 計(jì)算機(jī)上的 GCC 演示,因?yàn)?GCC 已經(jīng)做了兼容,其他編譯器也是如此。但是也有其他的 一些差異,比如 C89 不支持 long long int 類型,但是C99 是支持的。盡管如此,一些 編譯器(包括 GCC)支持了 C89 使用 long long int 類型,但是在C89 下使用要非常小 心,并非所有編譯器都支持。如果要使用 long long int 類型,最好使用C99 、C11 或 C18。

我們建議你始終使用 -Wall、-Wextra和 -pedantic 選項(xiàng)編譯程序。

3.4 使用系統(tǒng)調(diào)用

在任何關(guān)于 UNIX 和 Linux 的討論中,系統(tǒng)調(diào)用都是一個令人興奮的話題。它是Linux系統(tǒng)編程最底層的部分之一。我們按圖3.1 從上往下看,運(yùn)行的 shell 和二進(jìn)制在最上層, 在它的下面是標(biāo)準(zhǔn)C 庫函數(shù),比如 printf()、fgets()、putc() 等。在 C 庫的下面(即 最底層)有系統(tǒng)調(diào)用,比如 creat()、write() 等。

21d98fc0-c23a-11ed-bfe3-dac502259ad0.png

圖 3.1 高級函數(shù)和底層函數(shù)

我在本書中討論的系統(tǒng)調(diào)用是指內(nèi)核提供的C 函數(shù)系統(tǒng)調(diào)用,而不是實(shí)際的系統(tǒng)調(diào)用表。我們在這里使用的系統(tǒng)調(diào)用是用戶態(tài)調(diào)用的,但是函數(shù)本身是在內(nèi)核態(tài)執(zhí)行的。

很多標(biāo)準(zhǔn) C 庫的函數(shù)在實(shí)現(xiàn)中調(diào)用了一個或多個系統(tǒng)調(diào)用。putc() 函數(shù)是一個很好 的例子,它使用 write() 在屏幕上打印一個字符(這是一個系統(tǒng)調(diào)用)。還有一些標(biāo)準(zhǔn) C 庫函數(shù)根本不需要使用系統(tǒng)調(diào)用,比如 atoi(),它只需在用戶態(tài)執(zhí)行,不需要內(nèi)核幫它 執(zhí)行字符串轉(zhuǎn)換數(shù)字的操作。

一般來說,如果有可用的標(biāo)準(zhǔn) C 庫函數(shù),我們應(yīng)該優(yōu)先使用C 庫函數(shù)而不是系統(tǒng)調(diào)用。相比較而言,系統(tǒng)調(diào)用更難使用并且更加原始。一般來說,把系統(tǒng)調(diào)用視為底層接口,把C 庫函數(shù)視為高層接口。

但是,在某些情況下,我們必須使用系統(tǒng)調(diào)用,或者說在這些場景下使用系統(tǒng)調(diào)用更 方便。學(xué)習(xí)何時(shí)以及為什么使用系統(tǒng)調(diào)用會讓你成為更好的程序員。比如我們可以通過系 統(tǒng)調(diào)用在 Linux 上執(zhí)行很多文件操作,但是在其他地方是不行的。另一個使用系統(tǒng)調(diào)用的 例子是創(chuàng)建進(jìn)程的時(shí)候,詳見后文。總之,當(dāng)我們需要對系統(tǒng)進(jìn)行操作時(shí)就需要用到系統(tǒng) 調(diào)用。

3.4.1 準(zhǔn)備工作

在本節(jié)中,我們將使用 Linux 系統(tǒng)調(diào)用,所以你需要一臺 Linux 計(jì)算機(jī)。請注意, sysinfo() 系統(tǒng)調(diào)用在 FreeBSD 和 maxOS 下不可用。

3.4.2 實(shí)踐步驟

使用 C 庫的函數(shù)和使用系統(tǒng)調(diào)用實(shí)際上沒有太大區(qū)別,Linux 中的系統(tǒng)調(diào)用在頭文件 unistd .h 中聲明,所以我們在使用系統(tǒng)調(diào)用時(shí)需要包含這個文件。

1. 在文件中寫入以下代碼并將它命名為 sys-write .c。它用到了 write() 系統(tǒng)調(diào) 用。注意,代碼中沒有包含 stdio .h 頭文件,因?yàn)椴恍枰?printf() 函數(shù)或者任 何標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯誤文件流。我們直接輸出到1 號文件描述符(標(biāo)準(zhǔn)輸 出)。三個標(biāo)準(zhǔn)文件描述符總是被打開:

#include 
int main(void)
{
write(1,
"hello, world
", 13);
return 0;
}

2. 編譯代碼。為了寫出更好的代碼,從現(xiàn)在開始,我們會始終打開-Wall、-Wextra 和 -pedantic 參數(shù):

$>gcc-Wall-Wextra-pedantic -std=c99

>sys-write.c-o sys-write

3.運(yùn)行程序:

$> ./sys-write

hello, world

4. 編寫相同的代碼,只是用fputs()函數(shù)替代了 write() 函數(shù)。注意,我們在這里 包含了 stdio.h,而不是 unistd.h,將程序命名為 write-chars.c:

#include 
int main(void)
{
fputs("hello, world
", stdout);
return 0;
}

5.編譯程序:

$> gcc -Wall -Wextra-pedantic -std=c99

>write-chars .c -o write-chars

6.運(yùn)行程序:

$> ./write-chars

hello, world

7. 現(xiàn)在,我們編寫一個讀取用戶和系統(tǒng)信息的程序。把程序另存為 my-sys .c。代碼 示例中所有的系統(tǒng)調(diào)用都加粗顯示了。這個程序會獲取你的用戶ID、當(dāng)前工作目 錄、機(jī)器總內(nèi)存和可用隨機(jī)存儲內(nèi)存 (RAM),以及當(dāng)前的進(jìn)程 ID (PID ):

#include 
#include 
#include 
#include 
int main(void)
{
char cwd[100] = { 0 }; /* for current dir */
struct sysinfo si;
/* for system information */
aetc叢g(c叢g、 T00):/* get current working dir*/
e入eT對Eo(CeT):/* get system information
* (linux only)
*/
printf("Youruser ID is %d
",aet門Tg());
printf("Your
effective user ID is %d
",
aete門Tg());
printf("Your
current working directory is %s
",
cwd);
printf("Yourmachine has %ld megabytes of "
"total RAM
", si.totalram / 1024  / 1024);
printf("Yourmachine has %ld megabytes of "
"free RAM
", si.freeram / 1024 / 1024);
printf("Currently, there are %d processes "
"running
",si.procs);
printf("Thisprocess ID is %d
",aetbTg());
printf("The
parent process ID is %d

"aetbbTg());
return 0;
}

8.編譯程序:

t> acc -Mg丁丁 -Mext泥g -begg對tTc -etg=coo m入-e入e .c -o /

>m入-e入e

9.運(yùn)行程序,你會看到用戶信息和機(jī)器信息:

t> .m入-e入e
Your user ID is 1000
Your effective user ID is 1000
Your current workingdirectory is /mnt/localnas_disk2/
linux-sys/ch3/code
Your machine has 31033
megabytes of total RAM
Your machine has 6117
megabytes of free RAM
Currently,there are 2496 processes running
This process ID is 30421
The parent processID is 11101

3.4.3 它是如何工作的

在實(shí)踐步驟的第 1~ 6步中,我們了解了 write() 和 fputs() 函數(shù)之間的區(qū)別。區(qū) 別可能不那么明顯,但是 write() 系統(tǒng)調(diào)用使用了文件描述符而不是文件流。這幾乎適用 于所有的系統(tǒng)調(diào)用。文件描述符比文件流更加原始。同樣,自頂而下的方法也適用于文件描述符和文件流。文件流在文件描述符上層,并提供了高級別的接口。但是,有時(shí)候我們 也需要直接使用文件描述符,因?yàn)樗鼈兲峁┝烁嗟目刂啤A硗?,文件流可以提供更?qiáng)大和更豐富的輸入和輸出(帶有格式化的輸出,比如, printf())。

在第 7 ~ 9 步中,我們編寫了一個程序來獲取系統(tǒng)信息和用戶信息。在這里包含了三 個特定于系統(tǒng)調(diào)用的頭文件:unistd .h、sys/types.h 和 sys/sysinfo.h。

unistd.h 是 UNIX 和 Linux 系統(tǒng)中常見的頭文件。sys/types .h 是系統(tǒng)調(diào)用中另 一個常見的頭文件,經(jīng)常用于從系統(tǒng)取值。這個頭文件包含了特殊的變量類型,比如用于 用戶 ID ( UID)的 uid_t、用于組 ID ( GID)的 gid_t。它們一般是int整型。還有用于 inode 編號的 ino_t、用于 PID的 pid_t 等。

sys/sysinfo .h 頭文件專門用于 sysinfo() 函數(shù),而且這個函數(shù)只適用于 Linux 系統(tǒng)調(diào)用,所以在其他UNIX 系統(tǒng)(例如 macOS 、Solaris 或 FreeBSD/OpenBSD/NetBSD) 下不起作用。這個頭文件聲明了 sysinfo 結(jié)構(gòu),我們通過調(diào)用 sysinfo() 函數(shù)獲取系統(tǒng) 信息。

我們在程序中使用的第一個系統(tǒng)調(diào)用是 getcwd(),用于獲取當(dāng)前工作目錄。函數(shù)有 兩個參數(shù):一個表示緩沖區(qū),用來保存路徑;另外一個是緩沖區(qū)的長度。

下一個使用的系統(tǒng)調(diào)用是只能在Linux 系統(tǒng)下工作的 sysinfo() 函數(shù)。這個函數(shù)包 含很多信息。當(dāng)函數(shù)執(zhí)行時(shí),所有的數(shù)據(jù)都會保存到 sysinfo 數(shù)據(jù)結(jié)構(gòu)中。包括系統(tǒng)正常運(yùn)行時(shí)間、平均負(fù)載、內(nèi)存總量、可用和已使用內(nèi)存、總的和可用交換空間、以及正在運(yùn) 行的進(jìn)程總數(shù)。在 man 2 sysinfo 中,可以找到有關(guān) sysinfo 數(shù)據(jù)結(jié)構(gòu)中的各種變量 及其數(shù)據(jù)類型的信息。在示例代碼的下半部分,我們還使用printf() 打印了其中的一些 變量,例如 si .totalram,它表示系統(tǒng)內(nèi)存的大小。

其余的系統(tǒng)調(diào)用都直接從printf() 函數(shù)中調(diào)用并打印出返回值。

3.4.4更多

在手冊中有很多系統(tǒng)調(diào)用的詳細(xì)信息。一個好的學(xué)習(xí)方式是查看 man 2 intro 和 man 2 syscalls。

提示
一般系統(tǒng)調(diào)用出錯時(shí)都返回 -1,檢查返回值是一個好辦法。

3.5 獲取 Linux 和類 UNIX 頭文件信息

Linux 和其他 UNIX 系統(tǒng)中有很多特定的函數(shù)和頭文件,一般來說,它們都是 POSIX 函數(shù),但是只能運(yùn)行 Linux 上的函數(shù),比如 sysinfo()。我們已經(jīng)在前面用到了 2 個 POSIX 文件:unistd.h 和 sys/types.h。因?yàn)樗鼈兪?POSIX標(biāo)準(zhǔn)的,所以適用于所有 類 UNIX 系統(tǒng),例如 Linux 、FreeBSD 、OpenBSD 、macOS和 Solaris。

在本節(jié)中,我們會更多地學(xué)習(xí)POSIX 頭文件的知識、它們的作用以及如何使用。我們 還會學(xué)習(xí)如何在手冊中查找這些頭文件的信息。

3.5.1 準(zhǔn)備工作

在本范例中,我們要學(xué)習(xí)在手冊中查找頭文件。如果你使用的是基于Fedora 的系統(tǒng), 例如 CentOS 、Fedora 或 Red Hat,默認(rèn)這些手冊頁已經(jīng)安裝在系統(tǒng)上。如果由于某些原 因它們丟失了,你可以用 root 權(quán)限或者 sudo 執(zhí)行 dnf install man-pages 重新安裝。

如果你使用的是基于 Debian 的系統(tǒng),例如 Ubuntu 或 Debian,默認(rèn)是不安裝這些手冊 的,需要按照下面的命令來安裝它們。

Debian

Debian 對于非自由軟件更加嚴(yán)格,所以我們需要做一些額外步驟。

1. 以 root 權(quán)限打開 /etc/apt/sources .list。

2. 在每行末尾的 main 后面加上 non-free (在 main 和 non-free 之間有一個空格)。

3.保存文件。

4. 以 root 權(quán)限執(zhí)行 apt update。

5. 以 root 權(quán)限執(zhí)行 apt installmanpages-posix-dev 安裝手冊。

Ubuntu

Ubuntu 和其他基于 Ubuntu 的發(fā)行版對非自由軟件沒有那么嚴(yán)格,所以我們可以直接 安裝對應(yīng)的軟件包。執(zhí)行 sudo apt install manpages-posix-dev。

3.5.2 實(shí)踐步驟

頭文件非常多,所以重要的是學(xué)習(xí)哪些頭文件是我們需要的以及如何查找它們的信息。通過閱讀手冊,可以知道如何列出所有的頭文件。接下來我們會介紹這些。

在前面的范例中,我們使用了 sysinfo() 和 getpid() 函數(shù)。這里將學(xué)習(xí)如何找到 系統(tǒng)調(diào)用的相關(guān)信息以及所需的頭文件。

1.首先,我們閱讀 sysinfo() 的手冊:

$>man 2sysinfo

在 SYNOPSIS 頭文件下面,我們看到下面 2 行:

#include

int sysinfo(structsysinfo *info);

2. 這指我們要包含 sys/sysinfo.h 才能使用 sysinfo() 函數(shù)。函數(shù)需要一個 sysinfo 的數(shù)據(jù)結(jié)構(gòu)作為參數(shù)。在 DESCRIPTION 中,可以看到 sysinfo 數(shù)據(jù)結(jié)構(gòu)的組成。

3.查閱 getpid()。這是一個 POSIX 函數(shù),因此有更多的信息:

$>man 2getpid

在 SYNOPSIS 下,需要包含兩個頭文件:sys/types .h 和 unistd .h。另外,該 函數(shù)返回一個 pid_t 類型的值。

4.我們繼續(xù)學(xué)習(xí),打開 sys/types .h 的手冊:

$> mansys_types.h

在 NAME 下,我們看到頭文件包含的數(shù)據(jù)類型。在 DESCRIPTION 下,可以看到 pid_t 數(shù)據(jù)類型用于進(jìn)程 ID 和進(jìn)程組 ID,但是沒有指明實(shí)際的數(shù)據(jù)類型。所以,讓 我們繼續(xù)向下滾動,直到找到一個寫著 Additionally 的副標(biāo)題。這里寫著 blksize_t、 pid_t 和 ssize_t 應(yīng)該是有符號整數(shù)類型。任務(wù)完成,現(xiàn)在我們知道它是一個有 符號整數(shù)類型,可以使用 %d 格式化運(yùn)算符來打印它。

5.我們進(jìn)一步學(xué)習(xí)。閱讀 unistd .h 手冊:

$>manunistd.h

6.在手冊中搜索 pid_t,可以找到更多關(guān)于它的信息:

輸入一個字符 /,再輸入 pid_t,按回車鍵搜索。按 n 搜索下一個出現(xiàn)單詞的位置。你會發(fā)現(xiàn)其他函數(shù)也返回 pid_t 類型,如 fork()、getpgrp() 和 getsid() 等。

7. 當(dāng)你閱讀unistd .h 手冊時(shí),可以看到頭文件中聲明的所有函數(shù);如果找不到,可 以搜索 Declarations。按 /,輸入 Declarations,然后按回車鍵。

3.5.3 它是如何工作的

手冊中 7posix 或 0p 特殊章節(jié)取決于你的 Linux 發(fā)行版,這部分內(nèi)容來自 POSIX Programmer’s Manual。比如,當(dāng)你打開 man unistd .h,可以看到 POSIX Programmer’s Manual,而不像打開 man 2 write 時(shí),你會看到 Linux Programmer’s Manual 。POSIX Programmer’s Manual 來自電氣電子工程師協(xié)會( IEEE)和開放組織,而不是來自 GNU 項(xiàng)目或 Linux 社區(qū)。

因?yàn)?POSIX Programmer’s Manual 不是自由的(就像在開源中一樣),Debian 選擇不把 它放在主軟件源中。這就是我們要添加 non-free庫到 Debian 中的原因。

POSIX 是 IEEE 制定的一組標(biāo)準(zhǔn)。該標(biāo)準(zhǔn)的目的是在所有 POSIX 操作系統(tǒng)(大多數(shù) UNIX 和類 UNIX 系統(tǒng))中實(shí)現(xiàn)一個通用的編程接口。如果你只在程序中使用 POSIX 函數(shù) 和 POSIX 頭文件,它將與所有其他UNIX 和類 UNIX 系統(tǒng)兼容。其實(shí)際實(shí)現(xiàn)可能因系統(tǒng)而 異,但整體功能應(yīng)該是相同的。

有時(shí),我們需要一些特定信息(比如pid_t 是哪種類型)時(shí),需要閱讀多個手冊。

這里的主要內(nèi)容是通過函數(shù)的手冊找到相應(yīng)的頭文件,然后根據(jù)頭文件的手冊找到更 多信息。

3.5.4更多

POSIX 頭文件手冊是手冊的一個特殊部分,沒有在man man 中列出。在 Fedora 和 CentOS 下,這部分稱為 0p;在 Debian 和 Ubuntu 下,它被稱為 7posix。

提示
你可以通過 apropos . 命令列出指定部分的所有手冊(點(diǎn)表示匹配所有)。
比如,要列出第 2 節(jié)中的所有手冊,輸入 apropos -s 2. (包括點(diǎn),它是命令的一 部分)。要列出 Ubuntu 下 7posix 特殊部分中的所有手冊,輸入 apropos -s 7posix.。

3.6 定義功能測試宏

在本節(jié)中,我們將學(xué)習(xí)一些常見的 POSIX 標(biāo)準(zhǔn)、如何使用它們、為什么要使用它們, 以及如何在功能測試宏中指定它們。

我們已經(jīng)學(xué)習(xí)了幾個包含 POSIX 標(biāo)準(zhǔn)以及一些特定 C 標(biāo)準(zhǔn)的示例了。例如,使用 getopt() 時(shí),在源代碼最頂部定義了 _XOPEN_SOURCE 500 (第 2 章中的mph-to- kph_v2.c 示例,它可以使程序更易于編寫腳本)。

功能測試宏控制那些出現(xiàn)在頭文件中的定義。我們可以通過兩種方式來使用,通過功 能測試宏阻止我們使用非標(biāo)準(zhǔn)的定義來構(gòu)建可移植的應(yīng)用程序,或者反過來,允許我們使 用非標(biāo)準(zhǔn)的定義。

3.6.1 準(zhǔn)備工作

我們將在本范例中編寫 2 個程序:str-posix.c 和 which-c.c。你可以從 https://github.com/PacktPublishing/Linux-System-Programming-Techniques/ tree/master/ch3 下載它們,也可以跟隨下文編寫它們。你還需要我們在第 1章中安裝 的 GCC 編譯器。

3.6.2 實(shí)踐步驟

這里,我們將學(xué)習(xí)功能測試宏、 POSIX 標(biāo)準(zhǔn)、 C 標(biāo)準(zhǔn),以及其他相關(guān)知識的內(nèi)部原理。

1. 把下面的代碼寫入文件,并將其命名為 str-posix.c。這個程序只簡單地使用 strdup() 復(fù)制一個字符串,并打印它。注意,我們在此處需要包含頭文件string.h :

#include 
#include 
int main(void)
{
char a[] = "Hello";
char *b;
b = strdup(a);
printf("b
= %s
", b);
return 0;
}

2.讓我們看看用 C99 標(biāo)準(zhǔn)編譯程序會發(fā)生什么。你會看到不止一條錯誤信息:

$> gcc -Wall -Wextra -pedantic -std=c99 
> str-posix.c-o str-posix
str-posix.c: In function 'main':
str-posix .c9: warning: implicit declaration of
function 'strdup'; did you mean 'strcmp'? [-Wimplicit-
function-declaration]
b = strdup(a);
^~~~~~
strcmp
str-posix.c7 assignment to 'char
*' from
'int' makes pointer from integer withouta cast [-Wint-
conversion]
b = strdup(a);

3. 這里產(chǎn)生了一個非常嚴(yán)重的警告,不過編譯成功了。如果我們嘗試運(yùn)行程序,它會 在某些發(fā)行版上失敗,但在某些發(fā)行版上不會。這就是所謂的未定義行為:

$>./str-posix

Segmentation fault

但是在另一些 Linux 發(fā)行版上,我們看到下面的輸出:

$>./str-posix

b = Hello

4. 現(xiàn)在到了最有趣,但同時(shí)也令人困惑的部分。這個程序崩潰的原因只有一個,但有

幾個可能的解決方案。我們都會在這里介紹。程序崩潰的原因是strdup()不是 C99 的一部分(我們將在 3.6.3節(jié)介紹為什么它有時(shí)會生效)。最直接的解決方案是 查看手冊,其中明確指出我們需要將 _XOPEN_SOURCE 功能測試宏設(shè)置為 500 或 更高。為了實(shí)驗(yàn),我們將它設(shè)置為 700 (我稍后會解釋原因)。在 str-posix .c 的 最頂部添加以下行,它需要在任何include 語句之前的第一行。否則,它將不起 作用:

#define _XOPEN_SOURCE700

5.現(xiàn)在你已經(jīng)添加了上述行,我們重新編譯程序:

$>gcc-Wall-Wextra-pedantic -std=c99

>str-posix.c-o str-posix

6. 現(xiàn)在沒有警告了,我們運(yùn)行程序:

$>./str-posix

b = Hello

7. 這是其中一種最顯而易見的解決方案。接下來刪除文件中的第一行(#define這行)。

8. 刪掉 #define 這行,我們重新編譯程序,但是這次,我們在編譯時(shí)設(shè)置功能測試 宏。通過 GCC中的 -D 參數(shù)設(shè)置:

$>gcc-Wall-Wextra-pedantic -std=c99

> -D_XOPEN_SOURCE=700 str-posix.c -o str-posix

9. 再次運(yùn)行程序:

$>./str-posix

b = Hello

10.這是第二個解決方案。但是當(dāng)我們用 man feature_test_macros 閱讀功能 測試宏的手冊時(shí),可以看到 _XOPEN_SOURCE 設(shè)置成 700或更大的值的效果和 _POSIX_C_SOURCE 設(shè)置成 200809L 或更大的值的效果是一樣的。我們現(xiàn)在嘗試 用 _POSIX_C_SOURCE 重新編譯程序:

$>gcc-Wall-Wextra-pedantic -std=c99

> -D_POSIX_C_SOURCE=200809L str-posix.c -o str-posix

11. 這樣也可以工作?,F(xiàn)在,我們用最后一種也是最危險(xiǎn)的解決方案。我們不設(shè)置任何 C 標(biāo)準(zhǔn)或任何功能測試宏,重新編譯程序:

$>gcc-Wall-Wextra-pedantic str-posix.c

>-ostr-posix

12.沒有警告, 我們運(yùn)行一下:

$>./str-posix

b = Hello

13. 當(dāng)我們只定義所有這些宏和標(biāo)準(zhǔn)時(shí),這到底如何工作?好吧,事實(shí)證明,當(dāng)我們不 設(shè)置任何 C 標(biāo)準(zhǔn)或功能測試宏時(shí),編譯器有一些默認(rèn)設(shè)置。為了證明這一點(diǎn),并了

解編譯器的工作原理,我們編寫以下程序。將它命名為 which-c .c。該程序?qū)⒋?印正在使用的 C 標(biāo)準(zhǔn)和定義的功能測試宏:

#include 
int main(void)
{
#ifdef __STDC_VERSION__
printf("Standard
C version: %ld
",
__STDC_VERSION__);
#endif
#ifdef _XOPEN_SOURCE
printf("XOPEN_SOURCE:
%d
",
_XOPEN_SOURCE);
#endif
#ifdef _POSIX_C_SOURCE
printf("POSIX_C_SOURCE:
%ld
",
_POSIX_C_SOURCE);
#endif
#ifdef _GNU_SOURCE
printf("GNU_SOURCE:
%d
",
_GNU_SOURCE);
#endif
#ifdef _BSD_SOURCE
printf("BSD_SOURCE:
%d
", _BSD_SOURCE);
#endif
#ifdef _DEFAULT_SOURCE
printf("DEFAULT_SOURCE:
%d
",
_DEFAULT_SOURCE);
#endif
return 0;
}

14.我們在不設(shè)置任何 C 標(biāo)準(zhǔn)和功能測試宏的情況下編譯并運(yùn)行程序:

$> gcc -Wall -Wextra -pedantic which-c.c -o which-c
$> ./which-c
Standard C version:
201710
POSIX_C_SOURCE: 200809
DEFAULT_SOURCE: 1

15. 我們指定編譯時(shí)使用 C99 標(biāo)準(zhǔn),然后重新編譯 which .c。這里編譯器會強(qiáng)制執(zhí)行 嚴(yán)格的 C 標(biāo)準(zhǔn)并禁止原來默認(rèn)會設(shè)置的一些功能測試宏:

$> gcc -Wall -Wextra -pedantic -std=c99 
> which-c.c -owhich-c
$> ./which-c
Standard C version:
199901

16.讓我們看看如果設(shè)置 _XOPEN_SOURCE 等于 600 會發(fā)生什么:

$> gcc -Wall -Wextra -pedantic -std=c99 
> -D_XOPEN_SOURCE=600 which-c.c -o which-c
$> ./which-c
Standard C version:
199901
XOPEN_SOURCE:
600
POSIX_C_SOURCE:
200112

3.6.3 它是如何工作的

在實(shí)踐步驟的第 1 ~ 10步中, 我們看到了使用不同的標(biāo)準(zhǔn)和功能測試宏時(shí)程序會發(fā)生 什么。我們還注意到在沒有指定任何C 標(biāo)準(zhǔn)或功能測試宏的情況下,編譯器也可以出人意 料地正常運(yùn)行。這是因?yàn)?GCC (以及其他編譯器)默認(rèn)設(shè)置了一些功能測試宏和 C 標(biāo)準(zhǔn)。但我們不能依賴這個默認(rèn)設(shè)置。自己指定總是更安全。這樣,我們知道它一定會起作用。

在第 13步中,我們編寫了一個程序來打印編譯時(shí)默認(rèn)設(shè)置的功能測試宏。為了防止編 譯器在未設(shè)置功能測試宏時(shí)產(chǎn)生錯誤,我們將所有printf() 行包裝在 #ifdef 和 #endif 語句中。這些語句是編譯器的if 語句,不是 C程序的 if 語句。例如以下行:

#ifdef _XOPEN_SOURCE
printf("XOPEN_SOURCE:
%d
", _XOPEN_SOURCE);
#endif

如果 _XOPEN_SOURCE 未定義,則編譯的預(yù)處理階段后不包含此printf() 行。反之 _XOPEN_SOURCE 被定義,它將被包括在內(nèi)。我們將在下一范例中介紹什么是預(yù)處理。

在第 14步中,我們看到在系統(tǒng)上,編譯器將 _POSIX_C_SOURCE 設(shè)置為 200809 時(shí) 有效。但是手冊說我們應(yīng)該將 _XOPEN_SOURCE 設(shè)置為 500 或更大。怎么會這樣呢?

如果我們閱讀功能測試宏的手冊( man feature_test_macros),會看到 _XOPEN_ SOURCE 設(shè)置成 700 或更大與 _POSIX_C_STANARD 設(shè)置為 200809 或更大的效果相同。由 于 GCC 已經(jīng)默認(rèn)設(shè)置 _POSIX_C_ STANDARD為 200809,所以這和 _XOPEN_SOURCE等于 700 具有相同的效果。

在第 15 步中,我們了解了當(dāng)指定一個標(biāo)準(zhǔn),比如 -std=c99 時(shí),編譯器會強(qiáng)制執(zhí)行 嚴(yán)格的 C 標(biāo)準(zhǔn)。這就是 str-posix .c 無法運(yùn)行(在編譯期間收到警告)的原因。它不是 一個標(biāo)準(zhǔn)的 C 函數(shù),而是 POSIX 函數(shù)。這就是為什么我們需要包含 POSIX 標(biāo)準(zhǔn)來使用它。當(dāng)編譯器使用嚴(yán)格的 C 標(biāo)準(zhǔn)時(shí),不會啟用其他功能。當(dāng)系統(tǒng)中的 C 編譯器支持 C99 時(shí), 這 會使我們編寫的代碼可以移植到所有系統(tǒng)。

在第 16 步中,我們在編譯程序時(shí)指定 _XOPEN_SOURCE 等于 600,這樣同時(shí)也會將 _POSIX_C_STANDARD 設(shè)置為 200112。我們可以在手冊( manfeature_test_macros) 中閱讀相關(guān)內(nèi)容:“ [ 當(dāng) ]_XOPEN_SOURCE 定義為大于或等于 500 的值時(shí) [...]以下宏 _POSIX_C_SOURCE 也會被隱式定義 [...]”。

功能宏有什么用呢,它們?nèi)绾斡绊懘a ?

系統(tǒng)頭文件里充滿了#ifdef 語句,功能測試宏是否設(shè)置決定了是否啟用和禁用各種 功能和特性。例如,當(dāng)我們使用 strdup() 函數(shù)時(shí),string .h 頭文件有包含在 #ifdef 語句中的strdup() 函數(shù)。這些語句檢查是否定義了 _XOPEN_SOURCE 或其他一些 POSIX標(biāo)準(zhǔn)。如果未指定此類標(biāo)準(zhǔn),則 strdup() 不可見。這就是功能測試宏的工作原理。

但是在第 3 步中,為什么程序在某些發(fā)行版上執(zhí)行會報(bào)段錯誤,有些則不會 ? 就像前 文提到的,如果沒有功能測試宏,代碼是沒有 strdup() 的聲明的,發(fā)生的事情是不確定 的。由于某些特定的實(shí)現(xiàn)細(xì)節(jié),它可能會起作用,也可能不起作用。當(dāng)我們編程時(shí),應(yīng)該始終避免未定義的行為。某些程序可以在特定的 Linux 發(fā)行版上運(yùn)行,這并不能保證它可 以在其他發(fā)行版的計(jì)算機(jī)上運(yùn)行。因此,我們應(yīng)該始終努力按照標(biāo)準(zhǔn)編寫正確的代碼。這樣才能避免未定義的行為。

3.6.4更多

我們定義的所有這些功能測試宏都應(yīng)該對應(yīng)于 POSIX 或其他標(biāo)準(zhǔn)。這些標(biāo)準(zhǔn)背后的思 想是在不同的 UNIX 版本和類 UNIX 系統(tǒng)之間創(chuàng)建一個統(tǒng)一的編程接口。

如果你想要深入研究標(biāo)準(zhǔn)和功能測試宏,有一些優(yōu)秀的手冊可以閱讀。例如:

man 7 feature_test_macros[這里可以閱讀到所有功能測試宏對應(yīng)的特定標(biāo) 準(zhǔn),例如 POSIX 、Single Unix Specification 、XPG (X/Open Portability Guide)等 ]

man 7 standards (有關(guān)標(biāo)準(zhǔn)的更多信息)

man unistd .h

man 7 libc

man 7 posixoptions

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11420

    瀏覽量

    212319
  • C語言
    +關(guān)注

    關(guān)注

    180

    文章

    7628

    瀏覽量

    139695
  • 源碼
    +關(guān)注

    關(guān)注

    8

    文章

    665

    瀏覽量

    30056
  • 編譯器
    +關(guān)注

    關(guān)注

    1

    文章

    1652

    瀏覽量

    49729
  • Makefile
    +關(guān)注

    關(guān)注

    1

    文章

    125

    瀏覽量

    19498

原文標(biāo)題:留言送書 | 深入探索 Linux 中的 C 語言(1)

文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    Linux內(nèi)核C語言宏的使用技巧

    Linux內(nèi)核可謂是集C語言大成者,從中我們可以學(xué)到非常多的技巧,本文來學(xué)習(xí)一下宏技巧,文章有點(diǎn)長,但耐心看完后C語言level直接飆升。
    發(fā)表于 07-21 14:56 ?613次閱讀
    <b class='flag-5'>Linux</b>內(nèi)核<b class='flag-5'>中</b><b class='flag-5'>C</b><b class='flag-5'>語言</b>宏的使用技巧

    [推薦]linux下的c語言編程簡介

    第一章本章將簡要介紹一下什么是Linux,C語言的特點(diǎn),程序開發(fā)的預(yù)備知識,LinuxC語言
    發(fā)表于 04-29 13:50

    Labview 深入探索

    Labview深入探索的很好資料哦
    發(fā)表于 04-27 21:29

    LabVIEW_深入探索

    LabVIEW_深入探索
    發(fā)表于 08-31 13:53

    LabVIEW 深入探索

    LabVIEW 深入探索
    發(fā)表于 07-01 10:54

    Linux的匯編語言

    在閱讀Linux源代碼時(shí),你可能碰到一些匯編語言片段,有些匯編語言出現(xiàn)在以.S為擴(kuò)展名的匯編文件,在這種文件,整個程序全部由匯編
    發(fā)表于 04-07 20:43 ?55次下載

    linux內(nèi)核C語言的編程風(fēng)格

    linux 內(nèi)核C語言的編程風(fēng)格
    發(fā)表于 09-26 14:22 ?0次下載

    嵌入式Linux與物聯(lián)網(wǎng)軟件開發(fā)C語言內(nèi)核深度解析書籍的介紹

    嵌入式Linux與物聯(lián)網(wǎng)軟件開發(fā)——C語言內(nèi)核深度解析 C語言是嵌入式Linux領(lǐng)域的主要開發(fā)
    發(fā)表于 05-15 18:10 ?9次下載
    嵌入式<b class='flag-5'>Linux</b>與物聯(lián)網(wǎng)軟件開發(fā)<b class='flag-5'>C</b><b class='flag-5'>語言</b>內(nèi)核深度解析書籍的介紹

    linux編譯c語言的方法

    以上就是linux如何編譯c語言的詳細(xì)內(nèi)容。
    發(fā)表于 06-09 08:58 ?1521次閱讀

    LinuxC語言編程入門教程詳細(xì)說明

    本文是LinuxC 語言編程入門教程。主要介紹了Linux 的發(fā)展與特點(diǎn)、C語言的基礎(chǔ)知識、
    發(fā)表于 08-25 18:05 ?39次下載
    <b class='flag-5'>Linux</b>下<b class='flag-5'>C</b><b class='flag-5'>語言</b>編程入門教程詳細(xì)說明

    基于LinuxC語言編程入門教程

    基于LinuxC語言編程入門教程
    發(fā)表于 06-15 10:56 ?19次下載

    C語言_Linux基本命令與C語言基礎(chǔ)

    這篇文章介紹在Linux環(huán)境下學(xué)習(xí)C語言搭建基本的環(huán)境過程,了解基礎(chǔ)的幾個命令使用方法,了解Linux下用戶權(quán)限配置,標(biāo)準(zhǔn)main函數(shù)傳參方式等等。
    的頭像 發(fā)表于 08-14 09:45 ?1341次閱讀

    Linux + C語言C語言獲取文件大小的方法都在這

    Linux + C語言C語言獲取文件大小的方法都在這
    的頭像 發(fā)表于 08-31 12:49 ?3735次閱讀
    【<b class='flag-5'>Linux</b> + <b class='flag-5'>C</b><b class='flag-5'>語言</b>】<b class='flag-5'>C</b><b class='flag-5'>語言</b>獲取文件大小的方法都在這

    Linux內(nèi)核中常用的C語言技巧有哪些

    Linux內(nèi)核采用的是GCC編譯器,GCC編譯器除了支持ANSI C,還支持GNU C。在Linux內(nèi)核,許多地方都使用了GNU
    的頭像 發(fā)表于 05-12 14:45 ?741次閱讀

    淺析Linux內(nèi)核中常用的C語言技巧

    Linux內(nèi)核采用的是GCC編譯器,GCC編譯器除了支持ANSI C,還支持GNU C。在Linux內(nèi)核,許多地方都使用了GNU
    發(fā)表于 06-25 10:46 ?644次閱讀