關於的筆記

重新閱讀<C專家編程>, 以下是一些筆記,覺得要重點掌握的。


1. 關於const的變量的賦值
foo(const char** p) { }
main(int argc, char** argv)
{
    foo(argv);  // ***
}

上面代碼段中//***會產生編譯錯誤。原因在於
const char** p = argv;
賦值是不被允許的。

C標準規定,兩個操作數都是指向有限定符或無限定符的相容類型的指針,左邊指針所指向的類型必須具有右邊指針所指向類型的全部限定符。
正是這個條件,使得函數調用中實參char*能夠與形參const char*匹配。
  • 左操作數是一個指向有const限定符的char的指針 (const是用來修飾char類型的,並不是修飾*指針類型的)
  • 右操作數是一個指向沒有限定符的char的指針
  • char類型與char類型是相容的,左操作數所指向的類型具有右操作數所指向類型的限定符(其實沒有),在加上自身的限定符(const)
類似地,const char**也是一個沒有限定符的指針類型(const並不是修飾指針)。它的類型是“指向有const限定符的char類型的指針(const用來修飾char)的指針。
由於char**和const char**都是沒有限定符的指針類型,但它們所指向的類型不一樣(前者指向char*,後者指向const char*),因此它們是不相容的。

2. 整形轉換
    if (-1 < (unsigned int)1) {
        fprintf(stderr, "-1 < (unsigned int)1)\n");
    } else {
        fprintf(stderr, "-1 > (unsigned int)1)\n");
    }

    if (-1 < (unsigned char)1) {
        fprintf(stderr, "-1 < (unsigned char)1)\n");
    } else {
        fprintf(stderr, "-1 > (unsigned char)1)\n");
    }
ANSI C給出的答案:
-1 > (unsigned int)1)  // -1被提升到(unsigned int)類型,所以就是(unsigned int)-1
-1 < (unsigned char)1) // -1類型不動,仍然是int類型,(unsigned char)1被提升到int類型

3. C語言的數組和函數聲明
1) 函數的返回值允許是一個函數指針,如:
int (* func())();  // 函數名字是func,參數是空,返回值是一個聲明爲int (*f)()的函數類型;
int (* func(double)) (const char*); // 函數名字是func,參數爲double,返回值是一個聲明爲int (*f)(const char*)的函數類型;
2) 函數的返回值允許是一個指向數組的指針,如: 
int (* foo())[]; // 函數名字是foo,參數爲空,返回值爲一個元素爲int類型的數組的指針
3) 數組裏面允許有函數指針,如int (* foo[])();
看到標誌符後面緊跟[],那就表明是一個數組。最後面是一個用()括起來的東西,那就表明數組的元素是一個函數指針,括起來的列表是函數的參數列表,最前面的就是函數的返回值類型。
4) 數組裏面允許有其它數組,所有你經常能看到int foo[][N] (但是必須聲明列的大小)。
typedef int (*array[])[];
int a[] = {1, 2}; 
int b[] = {3, 4};
array ab = {&a, &b};

typedef int (array1[])[2];
array1 ab1 = {0, 1, };

關於上面的判斷法則,作者專門寫了一個C程序來自動識別,以下是完整的代碼:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>

#define MAXTOKENS 100
#define MAXTOKENLEN 64

enum type_tag { IDENTIFIER, QUALIFIER, TYPE };

struct token {
    char type;
    char string[MAXTOKENLEN];
};

int top = -1;
struct token stack[MAXTOKENS];
struct token this;

#define pop stack[top--]
#define push(s) stack[++top] = s

enum type_tag classify_string()
{
    char* s = this.string;
    if (!strcmp(s, "const")) {
        strcpy(s, "read-only");
        return QUALIFIER;
    }

    if (!strcmp(s, "volatile")) return QUALIFIER;
    if (!strcmp(s, "void")) return TYPE;
    if (!strcmp(s, "char")) return TYPE;
    if (!strcmp(s, "signed")) return TYPE;
    if (!strcmp(s, "unsigned")) return TYPE;
    if (!strcmp(s, "short")) return TYPE;
    if (!strcmp(s, "int")) return TYPE;
    if (!strcmp(s, "long")) return TYPE;
    if (!strcmp(s, "float")) return TYPE;
    if (!strcmp(s, "double")) return TYPE;
    if (!strcmp(s, "struct")) return TYPE;
    if (!strcmp(s, "union")) return TYPE;
    if (!strcmp(s, "enum")) return TYPE;
    return IDENTIFIER;
}

void gettoken()
{
    char* p = this.string;
    while ((*p = getchar()) == ' ') ;

    if (isalnum(*p)) {
        while (isalnum(*++p = getchar()));
        ungetc(*p, stdin);
        *p = '\0';
        this.type = classify_string();

        return ;
    }

    if (*p == '*') {
        strcpy(this.string, "pointer to");
        this.type = '*';
        return ;
    }
    this.string[1] = '\0';
    this.type = *p;
    return;
}

void read_to_first_identifier()
{
    gettoken();
    while (this.type != IDENTIFIER) {
        push(this);
        gettoken();
    }

    printf("%s is ", this.string);
    gettoken();
}

void deal_with_arrays()
{
    while (this.type == '[') {
        printf("array ");
        gettoken();
        if (isdigit(this.string[0])) {
            printf("0..%d ", atoi(this.string) - 1);
            gettoken();
        }
        gettoken();
        printf("of ");
    }

}

void deal_with_function_args()
{
    while (this.type != ')') {
        gettoken();
    }
    gettoken();
    printf("function returning ");
}

void deal_with_pointers()
{
    while (stack[top].type == '*')
    {
        printf("%s ", pop.string);
    }
}

void deal_with_declarator()
{
    switch (this.type) {
        case '[' :
        deal_with_arrays(); break;
        case '(' :
        deal_with_function_args(); break;
    }

    deal_with_pointers();

    while (top >= 0) {
        if (stack[top].type == '(') {
            pop;
            gettoken();  // read chars after ')'
            deal_with_declarator();
        } else {
            printf("%s ", pop.string);
        }
    }
}

int main(int argc, char** argv)
{
    read_to_first_identifier();
    deal_with_declarator();
    printf("\n");
    return 0;
}

譬如輸入“const char* (*p[])(int);”, 它會告訴你:“p is array of pointer to function returning pointer to char read-only”。(cute?)

4. 數組和指針
char* p = "abcedfgh"; p[i];
編譯器將會:(進行2次解引用)
1) 取得符號表中p的地址,提取存儲於此處的指針;
2) 把下標所表示的偏移量與指針的值相加,產生一個地址;
3) 訪問上面這個地址,取得字符
char p[] = "abcdefg"; p[i];
編譯器符號表具有一個p的地址,取i的值,將它與這個地址相加,然後將相加的結果再取地址對應的值。

所以如果聲明extern char* p;而原先的定義是char p[10]; 這種情形。編譯器會把p當作一個指針,而且把對應位置上ascii字符解釋爲地址。
指針 數組
保存數據的地址 保存數據
間接訪問數據,首先取得指針的內容,把它作爲地址,然後從這個地址提取數據。
如果指針有一個下標[i],就把指針的內容加上i作爲地址,從中提取數據
直接訪問數據,a[i]只是簡單的以a+i爲地址取得數據
通常用於動態數據結構 通常用於存儲固定數據且數據類型相同的元素
通常指向匿名數據 自身即爲數據名

數組和指針在編譯器處理時是不同的,在運行時的表示形式也是不一樣的,並可能產生不同的代碼。對編譯器而言,一個數組就是一個地址,一個指針就是一個地址的地址。

1) 用a[i]這樣的形式對數組進行訪問總是被編譯器“改寫”或解釋爲像*(a+i)這樣的指針訪問。
2)指針始終就是指針。它絕不可以改寫成數組。你可以用下標形式訪問指針,一般都是指針作爲函數參數時,而且你知道實際傳遞給函數的是一個數組。
3)在特定的上下文中,也就是它作爲函數的參數(也只有這種情況),一個數組的聲明可以看作是一個指針。作爲函數參數的數組(就是在一個函數調用中)始終會被編譯器修改成爲數組第一個元素的指針。
4)因此,當把一個數組定義爲函數的參數時,可以選擇把它定義爲數組,也可以定義指針。不管選擇哪種方法,在函數內部事實上獲得都是一個孩指針。
5)在其他所有情況中,定義和聲明必須匹配。如果定義了一個數組,在其他文件對它進行聲明時也必須把它聲明爲數組,指針也是如此。

只有字符串常量纔可以初始化指針數組。指針數組不能由非字符串的類型直接初始化:
int* weights[] = { {1, 2, 3, 4, 5}, {6, 7}, {8, 9, 10} }; // failed to compile
如果想初始化非指針數組,可以先創建小維的數組,然後用小維的數組進行初始化更高維度的數組。

當這樣引用時:squash[i][j] 可以被聲明爲:
int squash[23][12];
或是
int* squash[23];
或是
int** squash;
或是
int (*squash)[12];  // 類型是int數組長度爲12的指針

實參 所匹配的形式參數
數組的數組 char c[8][10] char (*)[10]; 數組指針
指針數組 char *c[15]; char **c; 指針的指針
數組指針(行指針) char (*c)[64] char (*)[64]; 不改變
指針的指針 char** c; char** c; 不改變
之所以能在main()函數中看到char** argv這樣的參數,是因爲argv是個指針數組(即char* argv[])。這個表達式被編譯器改寫爲指向數組第一個元素的指針,也就是一個指向指針的指針。如果argv參數事實上被聲明爲一個數組的數組(也就是char argv[10][15]),它將被編譯器改寫爲char (*argv)[15] (也就是一個字符數組指針),而不是char** argv。

5. 鏈接
查找某一個特定的符號在哪個庫定義
cd /usr/lib
foreach i (lib?*)
    echo $i
    nm $i | grep $(symbol) | grep -v UNDEF
end

在動態鏈接和靜態鏈接的鏈接語義還存在一個額外的巨大區別,它經常會迷惑不夠仔細的用戶。在動態鏈接中,所有的庫符號進入輸出文件的虛擬地址空間中,所有的符號對於鏈接在一起的所有文件都是可見的。相反,對於靜態鏈接,在處理archive時,它只是在archive中查找載入器當時所知道的爲定義的符號。
簡而言之,在編譯器命令行中各個靜態鏈接庫出現的順序是非常重要的。

編譯器設計者採用一種稍微靈活的方法。我們從頂部增加或拿掉盤子,但我們也可以修改位於堆棧中部的盤子的值。函數可以通過參數或全局指針訪問它所調用的函數的局部變量。堆棧段有3個主要的用途,其中兩個跟函數有關,另一個跟表達是計算有關。
1. 堆棧爲函數內部聲明的局部變量提供存儲空間;
2. 進行函數調用時,堆棧存儲與此有關的一些維護性信息,通常稱爲堆棧幀,或者過程活動記錄;
3. 堆棧也可以被用作暫時存儲區。譬如一些臨時變量,會被壓倒堆棧中,然後再取出。通過alloca()函數來分配。

6. 內存管理
堆內存的回收不必與它所分配的順序一致(它甚至可以不回收),所以無序的malloc/free最終會產生堆碎片。堆對它的每塊區域都需要密切留心,那些是已經分配了的,哪些是尚未分配的。其中一種策略就是建立一個可用塊(“自由存儲區”)的鏈表,每塊由malloc分配的內存塊都在自己的前面表明自己的大小。有些人用arena這個術語描述由內存分配器管理的內存塊的集合。
被分配的內存總是經過對齊,以適合機器上最大尺寸的原子訪問,一個malloc請求申請的內存大小爲方便起見一般被圓整爲2的乘方。回收的內存可供重新使用,但並沒有辦法把它從你的進程移出交還給操作系統。
堆的末端由一個稱爲break的指針來標識。當堆管理器需要更多內存時,它可以通過系統調用brk和sbrk來移動break指針。一般情況下,不必由自己顯示地調用brk,如果分配的內存容量很大,brk最終會被自動調用。
你的程序可能無法同時調用malloc/brk。如果你使用malloc,malloc希望當你調用brk和sbrk時,它具有唯一的控制權。由於sbrk向進程提供了唯一的方法將數據段內存返回給系統內核,所以如果使用了malloc,就有效地防止了程序的數據段縮小的可能性。要想獲得以後能夠返回給系統內核的內存,可以使用mmap系統調用來映射/dev/zero文件。需要返回這種內存時,可以使用munmap系統調用。

ANSI標準第7.7.1.1節指出,在我們現在這種情況下,當信號處理程序調用任何標準庫函數時(printf),程序是行爲是未定義的。

7. 類型提升
一個會發生隱式類型轉換的地方就是參數傳遞。在K&R C中,由於函數的參數也是表達式,所以也會發生類型提升。在ANSI C中,如果使用了適當的函數原型,類型提升便不會發生,否則也會發生,在被調用函數的內部,提升後的參數被裁減爲原先聲明的大小。
這就是爲什麼單個的printf格式符字串%d能使用於幾個不同類型,short,char或int,而不論實際傳遞的是上述類型的哪一個。函數從堆棧(或寄存器中)取出的參數總是int類型,並在printf或其他被調用函數裏按統一的格式處理。如果使用printf來打印比int長的類型,就可以發現這個問題。

C語言也執行這項任務,但它同時也提升比規範類型int或double更小的數據類型,即使它們類型匹配。在隱式類型轉換方面,有三個重要的地方需要注意:
隱式類型轉換是語言中的一種臨時手段,起源於簡化最初的編譯器的想法。把所有操作數轉換爲統一的長度極大的簡化了代碼的生成。這樣,壓到堆棧中的參數都是同一長度的,所有運行時系統只需要知道參數的數目,而不需要知道它們的長度。

如果判斷一個數是無符號數? 可以根據類型提升來判斷
if (a < 0) { "無符號數" }
else if (-1 - a >= 0) { "無符號數" }  // 如果a是無符號數,-1和0將會被提升到無符號數,-1變成最大的無符號數,相減a肯定是個非負數
else { "有符號數“ } 

隨機選取文件中的一個字符串。字符串的個數是已知的。

網上的一個實現:

#define random(x) (rand() % x) //產生x內的隨機函數
#define RAND_N 1000

//自定義隨機器
void my_random(char *buf1, char *buf2, int count)
{
     //判斷範圍
     if(random(RAND_N) < RAND_N / count)
     {
          strcpy(buf1, buf2);
     }
}

//主函數
int main()
{
     FILE *fp;
     int count = 0;
     char buf1[100], buf2[100];
     
     if ((fp = fopen("d:/test.txt", "r")) == NULL) {
          fprintf(stderr, "open error");
          exit(0);
     }
     
     if(fscanf(fp, "%s", buf1) == EOF)
     {
          printf("file is null\n");
          return 0;
     }

     count++;
     
     srand((int)time(0));//設置隨機數種子,srand不能調用兩次以上
     for(count++; fscanf(fp, "%s", buf2) != EOF; count++)
     { 
          my_random(buf1, buf2, count);  
     }
     
     fclose(fp);
     printf("隨即讀取的字符串爲 : %s\n", buf1);
     
     return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章