重新阅读<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;
}