C語言 其它邊角知識

2018-09-29 18:43 更新

非局部跳轉

在 C 中,goto 語句是不能跨越函數的,而執(zhí)行這類跳轉功能的是 setjmp 和 longjmp 宏。這兩個宏對于處理發(fā)生在深層嵌套函數調用中的出錯情況是非常有用的。
此即為:非局部跳轉。非局部指的是,這不是由普通 C 語言 goto 語句在一個函數內實施的跳轉,而是在棧上跳過若干調用幀,返回到當前函數調用路徑的某個函數中。

#include <setjmp.h>
int  setjmp (jmp_buf env) ;  /*設置調轉點*/
void longjmp (jmp_buf env,  int val) ;  /*跳轉*/

setjmp 參數 env 的類型是一個特殊類型 jmp_buf。這一數據類型是某種形式的數組,其中存放 在調用 longjmp 時能用來恢復棧狀態(tài)的所有信息。因為需在另一個函數中引用 env 變量,所以應該將 env 變量定義為全局變量。
longjmp 參數 val,它將成為從 setjmp 處返回的值。

#include <stdio.h>  
#include <setjmp.h>  

static jmp_buf buf;  

void second(void)   
{  
    printf("second\n");  
    longjmp(buf,1);              
    // 跳回setjmp的調用處使得setjmp返回值為1  
}  

void first(void)   
{  
    second();  
    printf("first\n");            
    // 不可能執(zhí)行到此行  
}  

int main()   
{     
    if (!setjmp(buf))   
    {  
        // 進入此行前,setjmp返回0  
        first();  
    }   
    else   
    {     
        // 當longjmp跳轉回,setjmp返回1,因此進入此行  
        printf("main\n");  
    }  

    return 0;  
}

直接調用 setjmp 時,返回值為 0,這一般用于初始化(設置跳轉點時)。以后再調用 longjmp 宏時用 env 變量進行跳轉。程序會自動跳轉到 setjmp 宏的返回語句處,此時 setjmp 的返回值為非 0,由 longjmp 的第二個參數指定。
一般地,宏 setjmp 和 longjmp 是成對使用的,這樣程序流程可以從一個深層嵌套的函數中返回

變長參數列表

\ 頭文件定義了一些宏,當函數參數未知時去獲取函數的參數變量: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 來獲取下一個參數。

//求任意個整數的最大值
#include <stdio.h>  
#include <stdarg.h>  

int maxint(int n, ...)         /* 參數數量由非變長參數n直接指定 */  
{  
    va_list ap;  
    int     i, arg, max;  

    va_start(ap, n);           /* ap為參數指針,首先將其初始化為最后一個具名參數, 以便va_arg獲取下一個省略號內參數 */  
    for (i = 0; i < n; i++) {  
        arg = va_arg(ap, int); /* 類型固定為int, 按照給定類型返回下一個參數 */  
        if (i == 0)              
            max = arg;             
        else {  
            if (arg > max)  
                max = arg;  
        }  
    }  
    va_end(ap);  
    return max;  
}  

void main()  
{  
    printf("max = %d\n", maxint(5, 2, 6, 8, 11, 7));     
}  

可變長數組

歷史上,C語言只支持在編譯時就能確定大小的數組。程序員需要變長數組時,不得不用malloc或calloc這樣的函數為這些數組分配存儲空間,且涉及到多維數組時,不得不顯示地編碼,用行優(yōu)先索引將多維數組映射到一維的數組。
ISO C99引入了一種能力,允許數組的維度是表達式,在數組被分配的時候才計算出來

#include <stdio.h>  

int  main(void)  
{  
    int n, i ;  

    scanf("%d", &n) ;   

    int array[n] ;   
    for (; i<n; i++)  
    {  
        array[i] = i ;  
    }  

    for (i=0; i<n; i++)  
    {  
        printf("%d,", array[i]) ;  
    }  

    return 0;  
} 

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

switch語句中的case

case支持范圍取值(gcc擴展特性)

#include <stdio.h>  

int  main(void)  
{  
    int i=0;   
    scanf("%d", &i) ;  

    switch(i)  
    {  
     case 1 ... 9:  putchar("0123456789"[i]);     
     case 'A' ... 'Z':    //do something  
     }   

     return 0;  
}  

case 關鍵詞可以放在if-else或者是循環(huán)當中

switch (a)  
{  
    case 1:    ;  
      // ...  
      if (b==2)  
      {  
        case 2:;  
        // ...  
      }  
      else case 3:  
      {  
        // ...  
        for (b=0;b<10;b++)  
        {  
          case 5:   ;  
          // ...  
        }  
      }  
      break;  

    case 4:  
} 

指定初始化(C99)

在C99之前,你只能按順序初始化一個結構體。在C99中你可以這樣做:

struct Foo {
    int x;
    int y;
    int z;
};
Foo foo = {.z = 3, .x = 5};

這段代碼首先初始化了foo.z,然后初始化了foo.x. foo.y 沒有被初始化,所以被置為0。
這一語法同樣可以被用在數組中。以下三行代碼是等價的:

int a[5] = {[1] = 2, [4] = 5};
int a[]   = {[1] = 2, [4] = 5};
int a[5] = {0, 2, 0, 0, 5};

受限指針(C99)

關鍵字 restrict 僅對指針有用,修飾指針,表明要修改這個指針所指向的數據區(qū)的內容,僅能通過該指針來實現(xiàn),此關鍵字的作用是使編譯器優(yōu)化代碼,生成更高效的匯編代碼。

int foo (int* x, int* y)  
{  
    *x = 0;  
    *y = 1;  
    return *x;  
} 

很顯然函數foo()的返回值是0,除非參數x和y的值相同??梢韵胂?,99%的情況下該函數都會返回0而不是1。然而編譯起必須保證生成100%正確的代碼,因此,編譯器不能將原有代碼替換成下面的更優(yōu)版本

int f (int* x, int* y)  
{  
    *x = 0;  
    *y = 1;  
    return 0;  
} 

現(xiàn)在我們有了 restrict 這個關鍵字,就可以利用它來幫助編譯器安全的進行代碼優(yōu)化了,由于指針 x 是修改 x的唯一途徑,編譯起可以確認 “ y=1; ”這行代碼不會修改 * x的內容,因此可以安全的優(yōu)化。

int f (int *restrict x, int *restrict y)  
{  
    *x = 0;  
    *y = 1;  
    return 0;  
}  

很多C的庫函數中用restrict關鍵字:
void memcpy( void restrict dest ,const void restrict src,sizi_t n)
這是一個很有用的內存復制函數,由于兩個參數都加了 restrict 限定,所以兩塊區(qū)域不能重疊,即 dest 指針所指的區(qū)域,不能讓別的指針來修改,即 src 的指針不能修改. 相對應的別一個函數 memmove(void dest,const void * src,size_t)則可以重疊。

靜態(tài)數組索引(C99)

void f(int a[static 10]) {
    /* ... */
}

你向編譯器保證,你傳遞給 f 的指針指向一個具有至少10個 int 類型元素的數組的首個元素。我猜這也是為了優(yōu)化;例如,編譯器將會假定 a 非空。編譯器還會在你嘗試要將一個可以被靜態(tài)確定為 null 的指針傳入或是一個數組太小的時候發(fā)出警告。

void f(int a[const]) {
    /* ... */
}

你不能修改指針 a.,這和說明符 int * const a.作用是一樣的。然而,當你結合上一段中提到的 static 使用,比如在int a[static const 10] 中,你可以獲得一些使用指針風格無法得到的東西。

多字符常量

int x = 'ABCD' ;  

這會把 x 的值設置為 0×41424344(或者0×44434241,取決于大小端)我們一般的小端機上,低位存在低字節(jié)處,DCBA 依次從低字節(jié)到高字節(jié)排列。
這只是一種看起來比較炫酷的寫法,一般沒什么用。

以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號