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

變長參數列表

<stdarg.h> 頭文件定義了一些宏,當函數參數未知時去獲取函數的參數/<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語言不常用的特性你知道嗎?

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

其語法格式如下:

# pragma token-sequence

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

3:#line

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

形式1

語法格式如下:

# line constant “filename”

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

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

提示如下的編譯信息:

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

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

形式2

語法格式如下:

# line constant

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

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個字節

注:正在學習C/C++的小夥伴需要學習資料的可以私信“資料”免費分享全套學習資料哦


分享到:


相關文章: