一、C:穿越时空的迷雾

1. C标准中定义了描述编译器的特点的一些术语:

(1) 由编译器定义的(imprementation-defined)

由编译器设计者决定如何处理。例如:整型数右移时要不要扩展符号位。(vc6.0中是扩展的)

(2) 未确定的(unspecified)

在某些正确情况下的做法,但标准并未规定应该怎样做。例如:参数求值的顺序。

(3) 未定义的(undefined)

在某些不正确情况下的做法,但标准并未规定应该怎样做。例如:当一个有符号整数溢出时该如何处理。

(4) 约束条件(a constraint)

必须遵守的限制或要求。例如:%操作符的操作数必须属于整型。

(5) 严格遵循标准的(strictly-confirming)

一个严格遵循标准的程序应该是:a.只使用已确定的特性;b.不突破任何由编译器实现的限制;c.不产生任何依赖于由编译器定义的或未确定的或未定义的特性的输出。

(6) 遵循标准的(confirming)

一个遵循标准的程序可以依赖一些某种编译器特有的不可移植的特性。

2. ANSI C标准对编译器制定了一些限制条件,比如函数定义中形参数量的上限至少可以达到31个、在表达式中至少可以支持32层嵌套的括号等。

3. 有时必须非常专注地阅读ANSI C标准才能找到某个问题的答案。作者举了一个例子:比如把char**变量的值赋给const char**时,编译器会发出错误警告,为什么呢?

char alpha = 'c';
char *pc = α
char **ppc = &pc;
const char **ppcc1 = ppc; // error,ppcc1 points to a pointer whose type is const char*
char *const *ppcc2 = ppc; // ok, ppcc2 points to a pointer whose type is char* const
char ** const ppcc3 = ppc; // ok, ppcc3 points to a pointer whose type is char*

C标准中的描述:“要使指针的赋值形式合法,必须满足:两个操作数都是指向有限定符或无限定符的相容类型的指针,左边指针所指向的类型必须具有右边指针所指向类型的全部限定符(指const或volatile)”。注意,const char类型有限定符const,而const char *类型并没有限定符,它的类型是“指向一个有const限定符的char类型的指针”。同理,const char **同样没有限定符。由于char **和const char **都是没有限定符的指针类型,但它们所指向的类型不一样(前者是char *,后者是const char *),所以它们是不相容的,不能相互转换。

4. const实际上表示read-only,在一个符号前加上const限定符只是表示这个符号不能被修改,但const并不能把变量变成常量,因此在C语言中不能用const int型整数来定义数组的长度,尽管在C++中这是允许的。

5. sizeof返回的是unsigned int型。

#define TOTAL_ELEMENTS (sizeof(array)/sizeof(array[0]))
int array[] = {,,,,};
int d = -;
/* ... */
if (d <= TOTAL_ELEMENTS - ) // d被强制转换为一个大数
x = array[d+];

二、这不是Bug,而是语言特性

1. 多做之过

(1) switch语句缺省采用fall through(即如果case后面不加break就依次执行下去)在97%的情况下是失误,据统计缺省的fall through的使用频率只有3%。

另外注意,break跳出的是最近的那层循环语句或switch语句。

switch (line) {
case THING1:
doit1();
break;
case THING2:
if (x == STUFF) {
do_first_stuff();
if (y == OTHER_STUFF)
break;
do_later_stuff();
}
initialize_modes_pointer(); // 代码的意图是跳到这里
break;
default:
proccessing();
} // 事实上是跳到这里
use_modes_pointer(); //导致modes-pointer未初始化

(2) 自动合并字符串

ANSI C规定相邻的字符串常量将被自动合并成一个字符串。但这也意味着字符串数组在初始化时如果不小心漏掉了一个逗号,编译器将不会发出错误信息,而是悄无声息地把两个字符串合并在一起。

//下面的指针数组只有两个元素,分别指向"Math"和"ChineseEnglish"
char *subject[] = {"Math", "Chinese" "English"};

(3) 允许数组末尾出现拖尾的逗号

char *fruit[] = {"apple", "banana", "orange",};  // ok

(4) 太多的缺省可见性

a. 定义C函数时,缺省情况下函数名是全局可见的,若想限制对该函数的访问,必须加个static。b.太大范围的全局可见性还影响到C语言的另一个特性:interpositioning,即用户编写与库函数同名的函数并取而代之的行为。c. C语言中对信息可见性的选择只有两种:all-or-nothing,一个符号要么全局可见,要么对其他文件都不可见。

2. 误做之过

(1) C语言中不少符号或关键字被重载而具有好几种意义,例如static,extern,void,*,&,()等等。

(2) "有些运算符的优先级是错误的",这些运算符是“当按照常规方式使用时,可能引起误会的任何运算符”。

*p.f  // .优先级高于*,故等价于*(p.f)
int *ap[] // []优先级高于*,故等价于int* (ap[])
int *fp() // ()优先级高于*,故等价于int* (fp())
(ip & mask != ) // 关系运算符高于逻辑运算符,故等价于ip & (mask != 0)
c = getchar() != EOF // 关系运算符高于赋值运算符,故等价于c = (getchar() != EOF)
msb << + lsb // 算符运算符高于移位运算符,故等价于msb << (4+lsb)
i = , //逗号运算符优先级最低,故等价于(i=1), 2

(3) 早期gets()中的Bug导致了Internet蠕虫。gets()函数并不检查缓冲区的空间,如果函数的调用者提供了一个指向堆栈的指针,并且gets()函数读入的字符数量超过了缓冲区空间,gets()函数将会愉快地将多出来的字符继续写入到堆栈中,从而覆盖堆栈原先的内容。

3.少做之过

(1) 标准参数处理:C语言不能区分运行时选项和其他参数。

(2) 空格

a. 当"\"转义为newline时,"\"后面有无空格无法看出来。

b. 有无空格对自增操作符的影响

z = y+++++x;    // error
z = y++ + ++x; // ok

c. 当程序员有两个指向int的指针并想对两个int数据执行除法运算时,"/"后面要有空格。

ratio = *x / *y  // ok
ratio = *x/*y // error, "/*" is the beginning of a comment

(3) 把lint程序从编译器中分离出来作为一个独立的程序是个严重的失误。应该“早用lint程序,勤用lint程序”。

三、分析C语言中的声明

1. C语言设计哲学:对象的声明形式与它的使用形式尽可能相似。

2. C语言声明的一个缺点:操作符的优先级设计不当、过于复杂,使人们无法以习惯的自然方式从左到右阅读一个声明。

3. C语言声明的名字

(1) 类型说明符:void, char, short, int, long, signed, unsigned, float, double, struct, enum

(2) 存储类型:extern, static, register, auto

(3) 类型限定符:const, volatile

4. 结构体

(1) 结构体的声明与结构体变量的定义应该分开写,以增加可读性。毕竟,我们只编写一次代码,但在以后的程序维护过程中将多次阅读它们。

(2) 参数传递时首先尽可能地存放在寄存器中。

(3) 若把数组放在结构体中,则对结构体赋值时,整个数组都会被赋值。

5. #define定义的名字一般在编译时就被丢弃,而枚举名字则通常在调试器中一直可见,可以在调试代码中使用它们。

6. C语言声明的优先级规则

(1) 声明从它的名字开始读取,然后按照优先级顺序依次读取

(2) 优先级从高到低依次是:

a. 声明中被括号括起来的那部分

b. 后缀操作符:()表示这是一个函数,[]表示这是一个数组

c. 前缀操作符:*表示“指向...的指针”

(3) 如果const或volatile关键字的后面紧跟类型说明符(如int,double),那么它作用于类型说明符,否则作用于它左边紧邻的指针星号。

7. typedef

(1) typedef和#define的区别

a. 可以用其他类型说明符对宏类型名进行扩展,但对typedef定义的类型名不能这样做。

#define peach int
unsigned peach i; // ok typedef int banana;
unsigned banana i; // error

b. 在连续几个变量的声明中,用typedef定义的类型能够保证声明中所有的变量均为同一种类型,而用#define定义的类型则无法保证

#define int_ptr int *
int_ptr chalk, cheese; // chalk is int *, while cheese is int typedef char * char_ptr;
char_ptr duck, mouse; // both of duck and mouse are char*

(2) 使用typedef的建议

a. 不要为了方便起见对结构体使用typedef,这样做唯一的好处是使你不必书写struct关键字,但这个关键字可以向你提示一些信息,你不应将其省掉。

b. typedef应该用在:数组、结构体、指针以及函数的组合类型;可移植类型。

c. 应该始终在结构体的定义中使用结构标签,这样可使代码更清晰。

四、数组和指针并不相同

1. 左值与右值

(1) 左值:出现在赋值符左边的符号,表示存储结果的地方,编译时可知。左值又分可修改与不可修改两种,数组名是不可修改的左值。

(2) 右值:出现在赋值符右边的符号,表示地址对应的内容,运行时可知。

2. 指针的外部声明与数组定义并不匹配

// file 1:
int mango[];
// file 2:
extern int *mango; // error, the type doesn't match

3. 指针与数组的区别

《C专家编程》读书笔记之第1~4章的更多相关文章

  1. c专家编程读书笔记

    无论在什么时候,如果遇到malloc(strlen(str));,几乎可以直接断定他是错误的,而malloc(strlen(str)+1):才是正确的: 一个L的NUL哟关于结束一个ACSII字符串: ...

  2. 类型解释器——C专家编程读书笔记

    对于声明,应该按下面的步骤来进行解释: 1) 声明从它的名字开始读取,然后按照优先级顺序依次读取 2) 优先级顺序 a) 括号括起来的部分 b) 后缀操作符,()表示函数,[]表示数组 c) 前缀操作 ...

  3. Node.js高级编程读书笔记Outline

    Motivation 世俗一把,看看前端的JavaScript究竟能做什么. 顺便检验一下自己的学习能力. Audience 想看偏后台的Java程序员关于前端JavaScript的认识的职业前端工程 ...

  4. 《android开发进阶从小工到专家》读书笔记--HTTP网络请求

    No1: 客户端与服务器的交互流程: 1)客户端执行网络请求,从URL中解析出服务器的主机名 2)将服务器的主机名转换成服务器的IP地址 3)将端口号从URL中解析出来 4)建立一条从客户端与Web服 ...

  5. python高级编程读书笔记(一)

    python高级编程读书笔记(一) python 高级编程读书笔记,记录一下基础和高级用法 python2和python3兼容处理 使用sys模块使程序python2和python3兼容 import ...

  6. C++Windows核心编程读书笔记

    转自:http://www.makaidong.com/%E5%8D%9A%E5%AE%A2%E5%9B%AD%E6%96%87/71405.shtml "C++Windows核心编程读书笔 ...

  7. CSAPP 并发编程读书笔记

    CSAPP 并发编程笔记 并发和并行 并发:Concurrency,只要时间上重叠就算并发,可以是单处理器交替处理 并行:Parallel,属于并发的一种特殊情况(真子集),多核/多 CPU 同时处理 ...

  8. DirectX 11游戏编程学习笔记之6: 第5章The Rendering Pipeline(渲染管线)

            本文由哈利_蜘蛛侠原创,转载请注明出处.有问题欢迎联系2024958085@qq.com         注:我给的电子版是700多页,而实体书是800多页,所以我在提到相关概念的时候 ...

  9. unix环境高级编程-读书笔记与习题解答-第一篇

    从这周开始逐渐的进入学习状态,每天晚上都会坚持写c程序,并且伴随对这本书的深入,希望能写出更高质量的读书笔记和程序. 本书的第一章,介绍了一些关于unix的基础知识,在这里我不想去讨论linux到底是 ...

  10. UNIX网络编程--读书笔记

    会集中这段时间写UNIX网络编程这本书的读书笔记,准备读三本,这一系类的文章会不断更新,一直会持续一个月多,每篇的前半部分是书中讲述的内容,每篇文章的后半部分是自己的心得体会,文章中的红色内容是很重要 ...

随机推荐

  1. 09_编写脚本,实现人机<石头,剪刀,布>游戏

    #!/bin/bashgame=(石头 剪刀 布)num=$[RANDOM%3]computer=${game[$num]}#通过随机数获取计算机的出拳#出拳的可能性保存在一个数组中,game[0], ...

  2. 【luoguSP3267】--莫队,不同数字个数

    题意翻译 给出一个长度为n 的数列,a1​​ a2​​ ,...an​ ,有q 个询问,每个询问给出数对(i,j),需要你给出ai​​ ai+1​​ ,...,aj​​ 这一段中有多少不同的数字 题目 ...

  3. redis系列(三):python操作redis

    1.安装包 pip install redis 2.使用 # -*- coding: utf-8 -*- # @Time : 18-12-7 下午4:33 # @Author : Felix Wang ...

  4. busTrace VS HW protocol analyzer - 好东西推荐

    最近在找PCIe/NVMe协议分析仪,发现一款软件分析仪:busTRACE,非常不错的工具,对于从事协议开发的同胞们,是个福利,下面把硬件和软件两种分析仪的各自的优势比较了一下(来自busTrace文 ...

  5. 八、linux文件系统上的特殊权限 SUID 、GUID、Sticky

    安全上下文 前提:进程有属主和属组,文件有属主和属组 任何一个可执行程序文件能不能启动为进程:取决发起者对程序文件是否拥有执行权限 启动为进程之后,其进程的属主为发起者:进程的属组为发起者所属的组 进 ...

  6. Java中的集合Collection

    集合是什么? Java集合类存放于 java.util 包中,是一个用来存放对象的容器. 注意:①.集合只能存放对象.比如你存一个 int 型数据 1放入集合中,其实它是自动转换成 Integer 类 ...

  7. asp.net core 控制静态文件的授权

    静态文件访问在网站中是一项重要的服务,用于向前端提供可以直接访问的文件,如js,css,文档等,方法是在Startup的Configure中添加UseStaticFiles()管道. 参考:ASP.N ...

  8. 黑马vue---56-58、vue组件创建的三种方式

    黑马vue---56-58.vue组件创建的三种方式 一.总结 一句话总结: 不论是哪种方式创建出来的组件,组件的 template 属性指向的模板内容,必须有且只能有唯一的一个根元素 1.使用 Vu ...

  9. 慢查询explan详解

    慢查询排查         show status;  // 查询mysql数据库的一些运行状态         show status like 'uptime'; // 查看mysql数据库启动多 ...

  10. Linux嵌入式学习过程(转载)

    嵌入式专业是一门实践性非常强的学科,只有多动手,多实践,多编程,多调试,多看书,多思考才能真正掌握好嵌入式开发技术.那么,如何从零开始学习嵌入式开发技术, 进入嵌入式开发大门呢,笔者根据自己的嵌入式学 ...