关于<C专家编程>的笔记

来源:互联网 发布:python 获取微秒 编辑:程序博客网 时间:2024/05/22 06:33
重新阅读<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 64enum 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] = senum 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;}


原创粉丝点击