2005年12月27日星期二

c专家编程、c和指针读书杂记

前段时间系统的看了两本书,随手记录了一些概念性的东西,以备查阅。
c 专家编程
char *pconst char *p1const char **p2char **p3
typedef char * char_ptr#define ptr char *
宏类型名可以用其他类型说明符进行扩展,typedef定义的不行:unsigned ptr a;是合理的unsigned char_ptr b;则不对在连续几个变量的声明中typedef定义的类型能够保证声明中所有的变量具有相同的类型。而#define类型定义则无法保证。宏只是单纯的替换。 不要对结构使用typedef,struct关键字可以给阅读这个代码的人更多的提示。数组和指针并不相同!定义: 只能出现在一个地方。 确定对象的类型并分配内存,用于创建新对象。例如:int my_array[100];声明: 可以多次出现 描述对象的类型,用于指代其他地方定义的对象。例如:extern int my_array[];nm程序可以列出函数库所包含的函数。
1、数据段保存在目标文件中
2、bss段不保存在目标文件中,目标文件只保存bss段在运行时需要的大小。
3、文本段最容易受优化的影响。
程序数据段保存初始化的全局和静态变量(包括bss段,bss段只保存没有值的变量,例如int a[20]将保存在bss段,而int a[20] = {1, 2, 3, .........}将保存在数据段)。堆栈用于保存局部变量、临时数据、传递到函数中的参数等。堆(heap)用于动态内存分配,调用malloc函数将在堆上分配内存。(static 局部变量保存在数据库区)虚拟地址空间的最低部分未被映射,也就是说它位于进程的地址空间内,但并未赋予物理地址,所以对他的任何引用都是非法的。典型情况下,它是从地址0开始的几k字节。它用于捕捉使用空指针和小整型指针引用内存的情况。
1、堆栈为函数内部声明的局部变量提供存储空间。按c语言的术语,这些变量被称为“自动变量”。堆中所有的东西都是匿名的--不能按名字访问,只能通过指针间接引用。
2、进行函数调用的时候,堆栈存储与此有关的一些维护性信息。(这些信息被称为堆栈结构(stack frame)或者过程活动记录(procedure activation record))。包括函数调用地址、任何不适合装入寄存器的参数以及一些寄存器值的保存。
3、堆栈也可被用作暂时存储区。(比如计算一个很长的算术表达式的时候用于保存中间结果)。通过alloca()分配的内存就位于堆栈中。如果想内存在函数调用后仍然有效就不能使用alloca()来分配内存(会被下一个函数调用覆盖)。
绝大多数的处理器中堆栈是向下增长的,也就是朝着低地址的方向生长。
检查源代码工具:
--------------------------------
cb c程序美化器,使源文件有标准的布局和缩进格式
indent 与cb作用相同
cdcel 分析c语言声明
cflow 打印程序中调用者和被调用者的关系
cscope 一个基于ascii码的c程序的交互式浏览器
ctags 创建一个标签文件,加快检查源程序的速度
lint c程序检查器
sccs 源代码版本控制系统
vgrind 格式器,用于打印漂亮的c列表
--------------------------------
用于监察可执行文件的工具:
--------------------------------
dis 目标代码反汇编工具
dump-Lv 打印动态链接信息
ldd 打印文件所需的动态链接信息
nm 打印目标文件的符号表
strings 查看嵌入于二进制文件中的字符串
sum 打印文件的检验和程序块计数
--------------------------------
帮助调试的工具:
--------------------------------
truss 打印可执行文件所进行的系统调用
ps 显示进程的特征ctrace 修改源文件,文件执行时按行打印
debugger 交互式调试器
file 显示一个文件包含的内容
--------------------------------
性能优化辅助工具:
--------------------------------
collector 在调试器控制下收集运行时性能数据(sunos 独有)
analyzer 分析已收集的性能数据(sunos独有)
gprof 显示调用图配置数据
prof 显示每个程序所消耗时间的百分比
tcov 显示每条语句执行次数的计数(确定一个函数中计算密集循环)
time 显示程序所使用的实际时间和cpu时间
--------------------------------
字符常量的类型是int型的,因此:printf("%d ", sizeof(''A'')); 的结果是4(32位机)而不是1;但在c++中字符常量的类型是char型的。
数组和指针可交换性总结:
1、用a[i]这样的形式对数组进行访问,总是被编译器“改写”或解释为像*(a+i)这样的指针访问。
2、指针始终是指针。它绝不可以改写成数组。你可以用下标形式访问指针,一般都是指针作为函数参数时,而且你知道实际传递给函数的是一个数组。
3、在特定的上下文环境中,也就是它作为函数的参数(只有这种情况),一个数组的声明可以看作是一个指针。作为函数参数的数组(在一个函数调用中)始终会被编译器修改成指向数组第一个元素的指针。
4、把一个数组定义为函数参数的时候,可以选择把它定义为数组,也可以选择把它定义为指针。不管选择哪种方法,在函数内部事实上获得的都是一个指针。
5、在其他所有情况中,定义和声明必须匹配。如果定义了一个数组,在其他文件对他进行声明时必须把它声明为数组,对于指针也一样。
只有字符串常量才可以初始化指针数组,
因此int *a[] = {
{1, 2, 3},
{1, 2},
{3, 4}
} //初始化失败
在实践中不要把realloc函数的返回值直接赋给字符指针,如果realloc()失败,他会使该指针的值变成NULL,这就无法对现有的表进行访问。
c++类的通常形式class name{
访问控制:声明
..........
访问控制:声明
..........
};
访问控制是一个关键字,它说明谁可以访问接下来声明的函数和数据。访问控制可以是:
1、public:属于public的声明在类的外部可见,并可按需要进行设置、调用和操纵。一般的原则是不要把类的数据做成public的,因为让数据保持私有才符合面向对象编程的理论:只有类本身才能改变自己的数据,外部函数只能调用类的成员函数,这就保证了类的数据只会以合乎规则的方式改变。
2、protected:属于protected的声明的内容只能由该类本身的函数以及从该类所派生的类的函数使用。
3、private:属于private的声明内容只能被该类的成员函数使用。private声明在类外部是可见的(名字是已知的)但却是不能访问的。另外还有两个关键字也会影响访问控制,friend和virtual。这两个关键字每次只能作用于一条声明。而且这两个关键字后面不跟冒号,属于friend的函数不属于类的成员函数,但可以像成员函数一样访问类的private和protected成员。friend可以是一个函数,也可以是一个类。virtual:告诉编译器该成员函数是多态的(也就是虚拟函数)类的成员函数可以在类中定义也可以在类外部定义
例如class Fruit{
public : void peel(){printf("in peel ")}
private: int weight;
};
class Fruit{
public : void peel();
private: int weight;
};
void Fruit::peel(){
printf("in peel ");
}
构造函数:绝大多数类都至少具有一个构造函数,当类的一个对象被创建的时候,构造函数被隐式的调用,它负责对象的初始化。
析构函数:是一个清理函数,当对象被销毁时,析构函数被自动调用。

c和指针
在程序中要屏蔽大段代码的时候使用
#if 0 statements
#endif
可以有效的将代码从程序中屏蔽,而使用/* */注释时可能会因为代码内部含有注释而失败。
字符串常量在表达式中使用时是使用它的地址,因此不能用字符串常量给数组进行赋值。
int *a, b, c;
int* a, b, c;
a是整形指针
typedef char * ptrtochar
ptrtochar a, b;
a, b都是字符指针
#define ptrtochar char *
ptrtochar a, b;
a是字符指针,b是字符
宏类型名可以用其他类型说明符进行扩展,typedef定义的不行
unsigned ptr a;是合理的
unsigned char_ptr b;则不对
标志符链接属性:
extern和static用于在声明中修改标志符的链接属性
static只对缺省链接属性为external的声明才有改变链接属性的效果
存储类型:auto static register
变量的缺省存储类型取决于它的声明位置。凡是在任何代码块之外声明的变量总是存储于静态内存中的,即不属于堆栈的内存,这类变量称为静态变量。对于这类变量,你无法为它指定其他的存储类型。
在代码块内部声明的变量的缺省类型是自动的。存储于堆栈中,称为自动变量。在程序执行到声明自动变量的代码块时,变量才被创建。当程序执行流离开该代码块时,自动变量将被销毁。如果代码块被多次执行,则自动变量也将创建多次。
对于自动变量,在声明时加上static关键字,可以使它的存储类型从自动变为静态。它将在整个程序执行期间一直存在。修改变量的存储类型并不表示修改该变量的作用域。函数行参不能声明为静态变量,因为实参总是在堆栈中传递给函数。(用于支持递归)
不显示指定初始值,静态变量将初始化为0。函数的局部变量在函数每次调用时,可能占据不同的位置。因此自动变量没有缺省的初始值。显示的初始化将在代码块的起始处插入一条隐含的赋值语句。
这种初始化的4个后果:
1、自动变量的初始化同赋值语句相比效率并没有变化(除了声明为const的变量之外),在声明变量的同时进行初始化和先声明后赋值只有风格之差,并无效率之别。
2、每次执行这个代码块都将重新进行初始化。
3、由于初始化是在运行时执行,可以使用任何表达式作为初始值
4、除非对自动变量进行显示的初始化,否则自动变量创建时,它们的值总是垃圾

static关键字:当用于不同的上下文环境的时候static具有不同的含义。用于函数定义或者代码块之外的变量声明时,static关键字用于修改标志符的链接属性,从external改为internal。标志符的存储类型和作用域不受影响,这种方式声明的函数和变量只能在声明它的源文件中访问。当用于代码块内部的变量声明的时候,static关键字用于修改变量的存储类型,从自动变量修改为静态变量,但变量的链接属性和作用域不变。这种方式声明的变量在程序执行之前被创建,并在整个程序执行期内一直存在。
作用域、链接属性和存储类型总结
-----------------------------------------
变量类型 声明的位置 是否存于堆栈 作用域 声明为static...
-----------------------------------------
全局 所有代码块之外 否 声明处到文件尾 不允许从其他源文件访问
-----------------------------------------
局部 代码块起始处 是 整个代码块 变量不存在堆栈中,值在程序整个执行期一直保持
-----------------------------------------
形式参数 函数头部 是 整个函数 不允许

++和--操作符都复制一份变量值的拷贝作为表达式的值。因此++a = 10是错误的,因为++a并不是一个左值。
/* When the system is little endian, it returns 1; otherwise 0.*/
int is_little_endian(){
int one = 1;
return *(char*)&one;
}
间接访问操作符(*)只能用于指针变量
标准允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针进行比较单不允许与指向数组第一个元素之前的那个内存位置的指针进行比较所以for (vp = &values[len - 1]; vp >= &values[0]; vp--) *vp = 0;存在一个问题:当数组的第一个元素被置零后,vp的值还将减1。接下去比较vp和&values[0]的结果是不确定的。因为vp到了数组边界之外。
int *func(void);void提示没有任何参数, 而不是表示它有一个类型为void的参数
抽象数据类型(abstract data type) ADT可以限制函数和数据定义的作用域这个技巧也被称为黑盒设计
指针使用效率:
1、当根据某个固定数目的增量在一个数组中移动的时候,使用指针变量将比使用下标产生效率更高的代码。当这个增量是1且机器具有地址自动增量模型时,这点表现更为突出。
2、声明为寄存器变量的指针通常比位于静态内存和堆栈中的指针变量效率更高(具体取决与所使用的机器)
3、如果可以通过测试一些已经初始化并经过调整的的内容来判断循环是否结束,那么就不要用一个单独的计数器。
4、那些必须在运行时求值的表达式较之&array[SIZE]、array+SIZE这样的常量表达式往往代价更高。
char m[]="hello";
char *m="hello";
第一种情况下"hello"是一个初始化列表,
而第二种情况下"hello"是一个字符串常量。
作为函数参数的多维数组多维数组每个元素本身就是一个数组,编译器需要知道它的维数,以便为函数行参的下标表达式进行求值。因此func(int (*a)[10]);才可以接收int s[2][10]作为参数。而写成func(int **a);是不对的。
一个文件中同时出现两个struct的声明
struct {
int a;
char b;
float c;
} *a;
struct {
int a;
char b;
float c;
} b;
a = &b;是非法的,这两个声明被编译器当作两种不同的类型。

联合的初始化只能初始化联合的第一个成员。
int fuc()[];这个函数是非法的,函数只能返回标量值,不能返回数组。
int f[]();也是非法的,数组元素必须具有相同的长度,而不同的函数显然可能具有不同的长度。int (*f[])();是合法的,首先f是一个数组,它的元素是某种类型的指针,数组元素的类型是个函数指针。它所指向的函数的返回值是整形。
int *(*f[])();和上一个类似,只不过数组元素指向的函数的返回值是整形指针。
出现在表达式中的字符串常量的值是一个常量指针。"0123456789ABCDEF"[1]

临近字符串自动连接
#define PRINT(FORMAT,VALUES) printf("the value of "#VALUES" is "FORMAT " ", VALUES)
int main()
{
PRINT("%d", 2+3);
PRINT("%s", "abc");
PRINT("%f", 1.1+4.21);
}
the value of 2+3 is 5
the value of "abc" is abc
the value of 1.1+4.21 is 5.310000
#define COMBINE(num,value) (a##(num) = (value))
char a1;
COMBINE(1,''c'');
printf("%c ", a1);
#undef name这条指令用于移除一个宏定义,如果一个现存的名字需要重新定义,那么它的旧定义首先必须用#undef移除。
条件编译
#if condition statements
#endif
是否定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol

没有评论: