C語言的角落——這些C語言不常用的特性你知道嗎?

linux-xiaofeng發表於2020-11-24

變長引數列表

<stdarg.h> 標頭檔案定義了一些巨集,當函式引數未知時去獲取函式的引數

變數:typedef va_list

巨集:

va_start()

va_arg()

va_end()

va_list型別通過stdarg巨集定義來訪問一個函式的參數列,引數列表的末尾會用省略號省略

(va_list用來儲存va_start,va_end所需資訊的一種型別。為了訪問變長引數列表中的引數,必須宣告va_list型別的一個物件 )

我們通過初始化(va_start)型別為va_list的參數列指標,並通過va_arg來獲取下一個引數。

【例子:】

求任意個整數的最大值:

C語言的角落——這些C語言不常用的特性你知道嗎?

可變長陣列

歷史上,C語言只支援在編譯時就能確定大小的陣列。程式設計師需要變長陣列時,不得不用malloc或calloc這樣的函式為這些陣列分配儲存空間,且涉及到多維陣列時,不得不顯示地編碼,用行優先索引將多維陣列對映到一維的陣列。

ISOC99引入了一種能力,允許陣列的維度是表示式,在陣列被分配的時候才計算出來。

C語言的角落——這些C語言不常用的特性你知道嗎?

注意:如果你需要有著變長大小的臨時儲存,並且其生命週期在變數內部時,可考慮VLA(Variable Length Array,變長陣列)。但這有個限制:每個函式的空間不能超過數百位元組。因為C99指出邊長陣列能自動儲存,它們像其他自動變數一樣受限於同一作用域。即便標準未明確規定,VLA的實現都是把記憶體資料放到棧中。VLA的最大長度為SIZE_MAX位元組。考慮到目標平臺的棧大小,我們必須更加謹慎小心,以保證程式不會面臨棧溢位、下個記憶體段的資料損壞的尷尬局面。

case支援範圍取值(gcc擴充套件特性) MinGW編譯通過

C語言的角落——這些C語言不常用的特性你知道嗎?

非區域性跳轉setjmp和longjmp

在C中,goto語句是不能跨越函式的,而執行這類跳轉功能的是setjmp和longjmp巨集。這兩個巨集對於處理髮生在深層巢狀函式呼叫中的出錯情況是非常有用的。

此即為:非區域性跳轉。非區域性指的是,這不是由普通C語言goto語句在一個函式內實施的跳轉,而是在棧上跳過若干呼叫幀,返回到當前函式呼叫路徑的某個函式中。

#include <setjmp.h>

int setjmp(jmp_buf env) ; /設定調轉點/

void longjmp(jmp_bufenv, int val) ; /跳轉/

setjmp引數env的型別是一個特殊型別jmp_buf。這一資料型別是某種形式的陣列,其中存放 在呼叫longjmp時能用來恢復棧狀態的所有資訊。因為需在另一個函式中引用env變數,所以應該將env變數定義為全域性變數。

longjmp引數val,它將成為從setjmp處返回的值。(很神奇吧。setjmp根據返回值可知道是哪個longjmp返回來的)

C語言的角落——這些C語言不常用的特性你知道嗎?
推薦自己的linuxC/C++交流群:812855908!整理了一些個人 覺得比較好的學習書籍、視訊資料以及大廠面經視訊共享在群檔案裡面,有需要的 可以自行新增哦!~
image.png
image.png
volatile屬性

如果你有一個自動變數,而又不想它被編譯器優化進暫存器,則可定義其為有volatile屬性。這樣,就明確地把這個值放在儲存器中,而不會被優化進暫存器。

setjmp會儲存當前棧狀態資訊,也會儲存此時暫存器中的值。(longjmp會回滾暫存器中的值)

【如果要編寫一個使用非區域性跳轉的可移植程式,則必須使用volatile屬性】

· IO緩衝問題

緩衝輸出和記憶體分配

當一個程式產生輸出時,能夠立即看到它有多重要?這取決於程式。

例如,終端上顯示輸出並要求人們坐在終端前面回答一個問題,人們能夠看到輸出以知道該輸入什麼就顯得至關重要了。另一方面,如果輸出到一個檔案中,並最終被髮送到一個行式印表機,只有所有的輸出最終能夠到達那裡是重要的。

立即安排輸出的顯示通常比將其暫時儲存在一大塊一起輸出要昂貴得多。因此,C實現通常允許程式設計師控制產生多少輸出後在實際地寫出它們。

這個控制通常約定為一個稱為setbuf()的庫函式。如果buf是一個具有適當大小的字元陣列,則

setbuf(stdout, buf);

將告訴I/O庫寫入到stdout中的輸出要以buf作為一個輸出緩衝,並且等到buf滿了或程式設計師直接呼叫fflush()再實際寫出。緩衝區的合適的大小在中定義為BUFSIZ。

因此,下面的程式解釋了通過使用setbuf()來講標準輸入複製到標準輸出:

C語言的角落——這些C語言不常用的特性你知道嗎?

不幸的是,這個程式是錯誤的,因為一個細微的原因。

要知道毛病出在哪,我們需要知道緩衝區最後一次重新整理是在什麼時候。答案:主程式完成之後,庫將控制交回到作業系統之前所執行的清理的一部分。在這一時刻,緩衝區已經被釋放了! (即main函式棧清空之後)

有兩種方法可以避免這一問題。

首先,使用靜態緩衝區,或者將其顯式地宣告為靜態:

static char buf[BUFSIZ];

或者將整個宣告移到主函式之外。

另一種可能的方法是動態地分配緩衝區並且從不釋放它:

char *malloc();

setbuf(stdout, malloc(BUFSIZ));

注意在後一種情況中,不必檢查malloc()的返回值,因為如果它失敗了,會返回一個空指標。而setbuf()可以接受一個空指標作為其第二個引數,這將使得stdout變成非緩衝的。這會執行得很慢,但它是可以執行的。

預編譯和巨集定義

C/C++中幾個罕見卻有用的預編譯和巨集定義

1:# error

語法格式如下:

#error token-sequence

其主要的作用是在編譯的時候輸出編譯錯誤資訊token-sequence,從方便程式設計師檢查程式中出現的錯誤。例如下面的程式

C語言的角落——這些C語言不常用的特性你知道嗎?

在編譯的時候輸出如編譯資訊

fatal error C1189: #error : No definedConstant Symbol CONST_NAME1

2:#pragma

其語法格式如下:

此指令的作用是觸發所定義的動作。如果token-sequence存在,則觸發相應的動作,否則忽略。此指令一般為編譯系統所使用。例如在Visual C++.Net 中利用# pragma once 防止同一程式碼被包含多次。

3:#line

此命令主要是為強制編譯器按指定的行號,開始對源程式的程式碼重新編號,在除錯的時候,可以按此規定輸出錯誤程式碼的準確位置。

形式1

語法格式如下:

其作用是使得其後的原始碼從指定的行號constant重新開始編號,並將當前檔案的名命名為filename。例如下面的程式如下:

C語言的角落——這些C語言不常用的特性你知道嗎?

提示如下的編譯資訊:

Hello.c(15) : error C2065: ‘CONST_NAME1’ :undeclared identifier

表示當前檔案的名稱被認為是Hello.c, #line 10 “Hello.c”所在的行被認為是第10行,因此提示第15行出錯。

形式2

語法格式如下:

其作用在於編譯的時候,準確輸出出錯程式碼所在的位置(行號),而在源程式中並不出現行號,從而方便程式設計師準確定位。

4:運算子#和##

在ANSI C中為預編譯指令定義了兩個運算子——#和##。

#define HI(x)printf(“Hi,”#x”\n”);

void main()

{

HI(John);

}

程式的執行結果

Hi,John

在預編譯處理的時候, #x的作用是將x替換為所代表的字元序列。(即把x巨集變數字串化)在本程式中x為John,所以構建新串“Hi,John”。

##的作用是串連線。

例如

#define CONNECT(x,y) x##y

void main()

{

int a1,a2,a3;

CONNECT(a,1)=0;

CONNECT(a,2)=12;

a3=4;

printf(“a1=%d\ta2=%d\ta3=%d”,a1,a2,a3);

}

程式的執行結果為

a1=0 a2=12 a3=4

在編譯之前, CONNECT(a,1)被翻譯為a1, CONNECT(a,2)被翻譯為a2。

資料型別對應位元組數

程式執行平臺

不同的平臺上對不同資料型別分配的位元組數是不同的。

個人對平臺的理解是CPU+OS+Compiler,是因為:

1、64位機器也可以裝32位系統(x64裝XP);

2、32位機器上可以有16/32位的編譯器(XP上有tc是16位的,其他常見的是32位的);

3、即使是32位的編譯器也可以弄出64位的integer來(int64)。

以上這些是基於常見的wintel平臺,加上我們可能很少機會接觸的其它平臺(其它的CPU和OS),所以個人認為所謂平臺的概念是三者的組合。

雖然三者的長度可以不一樣,但顯然相互配合(即長度相等,32位的CPU+32位的OS+32位的Compiler)發揮的能量最大。

理論上來講 我覺得資料型別的位元組數應該是由CPU決定的,但是實際上主要由編譯器決定(佔多少位由編譯器在編譯期間說了算)。

常用資料型別對應位元組數可用如sizeof(char),sizeof(char*)等得出

32位編譯器:

char :1個位元組

char*(即指標變數): 4個位元組(32位的定址空間是2^32, 即32個bit,也就是4個位元組。同理64位編譯器)

short int : 2個位元組

int: 4個位元組

unsigned int : 4個位元組

float: 4個位元組

double: 8個位元組

long: 4個位元組

long long: 8個位元組

unsigned long: 4個位元組

64位編譯器:

char :1個位元組

char*(即指標變數): 8個位元組

short int : 2個位元組

int: 4個位元組

unsigned int : 4個位元組

float: 4個位元組

double: 8個位元組

long: 8個位元組

long long: 8個位元組

unsigned long: 8個位元組

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章