《C/C++深层探索》读书笔记
1. 字节顺序:有如下代码:
int i = 0x00000041;
char *p = ( char * ) & i ;
printf ( “ %s / n ”, p ) ;
若为低位优先(Little-endian)则输出“A”,若为高位优先(Big-endian)无任何输出,因为数值为0的字节在C/C++里面表示一个单字节字符串的结束,由于指针指向的第一个字节的值是“ 0 ” ,所以系统认为这是一个什么都没有的空字符串。
2.外部变量和局部静态变量存放在(静态)数据区,在整个程序运行期间它们的地址不变,从程序开始到结束都是固定的,任何函数都能存取这些数据; 函数的形式参数及局部变量存放于栈中,一般地,栈是往内存低地址方向增长的。函数之间不能访问各自的静态内部变量。
3.主调函数m的局部变量b作为实参调用一个函数f,f函数不能修改主调函数的这个局部变量,此即为值传递,因为m调用f的时候先复制b,从而得到一个b的副本,再把这个副本赋给f的形式参数,f只是把b的值(即b的副本)压栈,f并不知道b的地址,f访问的只是一个b的副本,所以改变不了m中b这个局部变量。
4.声明一个变量仅仅是告诉编译器变量的类型,但不会导致编译器为这个变量分配存储空间,extern int x ; 是一个声明,而定义一个变量不仅告诉编译器变量的类型,而且编译器必须为变量分配空间,定义的同时还可以初始化,int b ; int c=4 ; extern int d=6 ; 均是定义。
5.链接属性指出了外部变量的可见范围,外部变量可以被多个函数访问,这些函数可以分布在不同的C源文件中。static关键字可以把外部变量的链接属性由外部改为内部,其他源文件的函数访问不到经过static修饰的外部变量。
6.在C89中,如果要表示一个没有任何参数的函数原型,一定要加上void,如: int test ( void )。
7.局部变量都被存放在栈里面,函数调用时存在,函数结束时消失。静态局部变量只会初始化一次,用来初始化它的值必须是编译期便可求出的常数。
8.C语言中函数默认是全局可见的,任何一个文件定义的函数都能在其他任何一个文件中调用, 但经过static修饰的函数只能被同一文件的函数调用,
9.函数原型的声明,全局变量的声明,自己定义的宏和类型均应该放在头文件中。
10.动态链接的意思就是在程序装入内存的时候才真正的把库函数的代码链接进来确定它的地址,并且即便有多个程序运行,内存中也只存在一份函数代码。
11.内存中的动态库中虽然只有一份副本,但动态库的数据仍然可能有多份副本,因为每一个链接到动态库的进程都可能会修改动态库的数据,每当出现这种情况的时候,OS就复制出一份数据的副本,然后修改进程的地址空间映射,使它指向新的数据副本,于是每个进程最后修改的只是属于自己的那份数据。
12.C语言默认浮点常数是double型的,它对整型变量扩展的关键是对被扩展变量的符号的解释,如果被扩展变量是带符号的,前面补1,若为无符号的,则前面补0 。
13.CPU对内存的访问并不是完全任意的,如果CPU想一次读写4个字节,则给定的内存起始地址最好是4的倍数。例如从0x00000126读取4个字节到寄存器,则CPU从0x00000124读4个字节,舍掉低16位放到寄存器低16位;再从0x00000128读4个字节,舍掉高16位放到高16位。 如int占4个字节,则凡是int变量的地址都是4的倍数,称为“4字节对齐”。另外,应该小心安排struct成员的布局从而节省空间。
14.指针是变量,编译器会为指针分配存储空间,并且指针的值可以在运行期间随意改变。而数组名只代表某段存储空间的起始地址,编译器不会为数组名字分配空间,即为一个常量,我们不可以改变数组名。
15.C编译器会把作为参数的数组名由数组类型转换成指针类型,在参数压栈后,数值名字相当于有了自己的存储空间,所以我们就可以像修改指针那样去改变数组名了。
16.预处理程序执行宏替换的单位是记号,而不是字符。编译器进行词法分析时遵循“最大匹配”原则,就是在遇到“空白”(如space、enter、tab)之前,以能够取得的、有意义的、最长的字符串作为记号。
17.预处理器的工作有:包含头文件、进行宏替换、去除注释及多余空白。
18.C语言中不允许嵌套注释,C99以及大部分编译器都已经支持单行注释,所以尽量使用单行注释,预处理器在碰到第一个“ */ ”时就认为注释已经结束。注释有三个原则:<1>字符串中的注释符不起注释作用;<2>注释符里的双引号不起标识字符串的作用;<3>如果情况混乱从头开始分析。
19.typedef同样具有作用域,并且内层的会屏蔽外层的。在同一作用域内,不能用相同的名字定义不同的数据类型,同样的typedef也不能重复出现(但C++中允许重复出现多次) 。typedef的作用范围最大只能是它所在的编译单元(源文件),不同源文件里的typedef相互独立,无任何限制。预处理器分析#define时值进行词法分析,而编译器对typedef不仅进行词法分析,还进行语法分析,用它定义的类型对声明或定义语句中的每一个变量都起作用。
20.C++能够把已用常量赋值的const变量看作编译期常数,C没有这种功能;C++默认const变量是内部的,而C默认是外部的;C只允许用常数初始化const外部变量,C++没有这种限制。
21.volatile用来修饰一个变量,它告诉编译器这个变量可能会被外部事件修改,那么编译器在优化代码时会考虑这一点。
22.字符串是常量的一种,不能被改变。字符串会被编译器安排到只读数据区,我们可以通过一个字符指针读取字符串但绝对不能修改字符串,否则就会发生运行期错误。当我们用字符串来初始化字符数组时,编译器在栈中为数组分配空间,然后把字符串中的所有字符复制到数组中。若有char a[ ] = ”abcde” ; a [0]= ’z’ ; 则语句a [0]= ’z’ ; 修改的不是只读数据区中的字符串常量,而是由字符串常量复制而来的存储于栈中的数组a的元素。
23.C语言中void的特点:<1>它可以和其它任何类型的指针(函数指针除外)相互转化而不需要类型强制转换;<2>我们不能对它进行解引用及下标操作。
24.C编译器对NULL通常这样实现:# define NULL (void *) 0 ,由于void指针可以不需要强制转换就赋值给任何其他类型的指针(函数指针除外),我们可以方便地使用NULL,如int *p = NULL; ,C++编译器对NULL的实现是 # define NULL 0,在C/C++中常数0能够无须任何强制类型转换就赋给任何类型指针。
25.C89添加了一个新的预处理指令# pragma,它的作用是为特定的编译器提供特定的编译指示(如优化代码的指示),这些指示是具体针对某一种编译器的,其他编译器可能不知道该指示的含义又或者对该指示有不同的理解,如果出现这种情况,编译器会忽略这条指令。
26.在C89中,局部变量必须在代码块的开始处声明,而在C99中允许我们在for语句的初始化表达式中声明变量,且这些变量的生存期只限于for语句。
27.C89中的一些隐含规则:<1>声明中不带类型关键字的变量被默认为是int变量;<2>没有声明返回值的函数定义,其返回值默认类型是int;<3>如果没有函数原型,对于传给函数的参数,整型变量默认调整为int压栈,浮点型默认调整为double再压栈,返回值则认为是int。
28.C99增加了布尔类型,用关键字“ _Bool ”标识,_Bool变量占用一个字节,它只有两个取值“ 1 ” 和“ 0 ” ,分别代表“真”和“假”。例如:_Bool b = ( a < b ) ;
29.C89中,数组方括号内必须是编译器可求出的常数值。而在C99中,还允许方括号里面是整型变量或整型表达式,这样的数组称为“变长数组”。 变长是指用变量或表达式声明(或定义)的数组,而数组的长度(像普通数组一样)在其生存期内是固定不变的。
30.变长数组的特点:<1>变长数组的sizeof运算的结果要在程序实际运行时才能求出;<2>不能在函数之外声明或定义变长数组,因为外部数组存放在数据区,它们占用的空间必须在编译期确定;<3>变长数组不能作为struct或union的成员,因为struct和union的大小必须在编译期确定,而变长数组大小是在运行期确定的;<4>必须保证变长数组方括号中的表达式返回整型值并且大于“ 0 ” ,否则结果是未定义的;<5>方括号里的表达式的求值顺序是不确定的。
31.C99新增了关键字“restrict”用来修饰指针,它主要是用来通知编译器这个指针指向的内存区域和其他同样用restrict修饰的指针指向的内存区域不存在重叠,于是编译器可以根据具体情况决定是否对代码进行优化编译。memcpy函数和memmove函数的区别就在于memcpy函数中两个指针参数指向的区域不能存在重叠。
32.涉及类型提升时,对于同一类型,带符号和无符号的级别是相同的。 _Bool , char , short , int,long , long long 的类型级别依次升高。
33.C++编译器会对函数的名字进行重新编码(修饰),如果需要调用C的库函数,就必须明确告诉C++编译器,通知它用C语言的方式对函数名进行修饰,如extern “C” int test ( int ) ;
34.有如下代码:# ifdef __cplusplus
extern “C”
{ # endif
int xxx ( ) ;
int yyy ( ) ;
# ifdef __cplusplus
}
# endif
根据是否定义了宏 __cplusplus,决定是否添加extern “ C ” 修饰符,如果宏被定义,则表明是C++编译器在处理代码,所以必须加上extern “ C ”,即用C语言的方式对函数名进行修饰;否则就不用添加,因为C编译器不认识extern “ C ” 。
35.函数模板不是真正意义的函数,我们不能把函数模板的声明和定义分开放在不同的文件里,正确的做法是把整个函数模板的定义放在一个头文件中,然后在需要调用函数的C++源文件中包含这个头文件。