《C专家编程》读书笔记

一、C的语言特性

   1. 多做

         1)switch语句,switch语句不会在每个case执行完毕后自动终止(称为fall through),这也是每个case语句最后加上break的原因;break语句跳出的是最近的循环语句或switch语句;

         2)相邻字符串自动连接,尤其在字符指针数组的初始化中,少了逗号会使两个字符串拼接在一起;

         3)缺省全局范围,当我们自己定义一个与库函数同名的函数时,会取而代之,不管在自己调用还是系统调用时,具体见“三、连接与链接”;

   2. 误做

        1)C 语言的简洁性 ,C关键字在多种上下文中使用,即符号重载,如static、&、()、void等

        2)运算符的优先级,优先级存在的问题:

           a. .的优先级大于*,->操作符用于消除这个问题;

           b. []的优先级高于*;

           c. 函数()高于*;

           d. ==和!=高于位操作符和赋值符;

           e. 算术运算高于移位运算;

           f. 逗号运算符在所有运算符中优先级最低;

         3)gets()函数接收任意长的参数会改写函数栈,超过的长度会污染别的栈帧(函数调用时的内存分配过程,以后详细看CSAPP);

   3. 少做

         1)命令行参数解析,选项开关"-"与文件名中的“-”混淆;

         2)空格引发的血案

             a. 多了空格,行末最后一个字符“\”后跟上换行符标识下一行为本行的后续,警惕“\”和换行符之间的空格

             b. 少了空格,造成歧义,例如z=y+++x,将被解析为 z= y++ + x;

             c. ratio = *x/*y,错误地编写了一个注释符号,语法器(?)报错;

         3)函数返回指向局部变量的指针,(C语言中自动变量在堆栈中分配内存,生命周期结束时回收);

             a. 返回一个指向字符串常量的指针;

             b. 使用全局声明的数组;

             c. 使用静态数组;(静态变量在静态数据区分配内存,深入看内存空间模型);

             d. 显示分配一些内存,保存返回的值;(感觉最常用到,每次调用都创建一个缓冲区,但需要记得释放,否则会导致内存泄漏);

              e. lint程序(?);

二、C语言的声明

   1、声明的结构

        1)声明器:零个或多个(指针)+ 有且只有一个(直接声明器) + 零个或一个(初始化内容);

        2)声明:至少一个(类型说明符) + 有且只有一个(声明器)+ 零个或多个(,+更多的声明器)+ 一个(分号);

        3)例如:int (*func())();

        4)声明的优先级规则;

《C专家编程》读书笔记_第1张图片

                                                                            图 1    声明的优先级规则

         5) 声明的状态机;

《C专家编程》读书笔记_第2张图片

     6)cdecl 程序(实践)

  2、typedef

         1)typedef unsigned long size_t; size_t long_number;

         2)typedef int x[10] 和 define x int[10]的区别: define为宏定义,编译时整体x替换为int[10];

         3)编写解析C语言声明的程序,类似前、中、后缀表达式的互换,利用优先级与栈;

三、编译与链接

   1、编译过程

         预处理 ====> 语法和语义分析 ===> 代码生成器 ===> 优化器 ===> 汇编程序 ===> 链接-载入器

         (此处应该深入查看中间过程文件的打印)

   2、链接过程

          程序经过编译但不链接接后,产生目标文件 *.o ; 此时程序需要将所有目标文件和库文件集中在一起,产生可执行文件(可以编写makefile控制编译、链接过程理解)。此时链接器确认main函数为程序开始执行的地方(此处可深入main函数的调用机制),把符号引用绑定到内存中固定的地址。

       1)静态链接

           函数库文件的一份拷贝是可执行文件的物理组成部分;

           缺陷:已有的函数库可能与运行的系统不兼容;

       2)动态链接

           可执行文件只包含文件名,程序运行时到指定目录寻找函数库;

          优点:

            a.  减小可执行文件体积,但运行速度稍慢,(strip 去掉调试信息也能缩小可执行文件的体积);

            b. 共享函数库的一个单独拷贝(映射到内存中的函数库被所有可使用它们的进程共享);

          位置无关的代码(?),如果不使用与位置无关的代码,每个全局引用就不得不在运行时通过修改页面安排到固定的位置,这就使得页面无法共享(每个使用共享库的进程一般会把它映射到不同的虚拟地址;

   3、关于函数库链接

       1)GNU编译器会用-L pathname来指定动态链接库的位置,以gcc为例,

      gcc test.c -o test -lmyBaic -L ./code/libs

        该语句链接./code/libs下的libmyBasic.so,生成可执行文件test.

   另外也可以指定环境变量,用来指定编译时查找动态库的位置,为LD_LIBRARY_PATH和LD_RUNPATH. 

    2)库约定

   预处理命令#include<头文件>,有些约定了头文件的查找路径(自动链接),而有些会需要动态链接.

        以Solaris 2.x为例:

     自动链接: /usr/lib/libc.so

          动态链接:   -lthread

      3)静态库与动态库的动作(链接语义)不同

           a.编译器命令行的符号解析是从左到右(C函数参数的内存分配是从右到左),静态库出现的顺序不同,结果就可能不同;同样,在代码之前引入静态库(gcc -lm main.c)会导致符号不会被提取(符号解析与提取过程),导致出错;

           b.所以,编译时应始终将-l指令参数放在命令行的最右边;

      4)interpositioning

            interposion和缺省全局域:使用interpositoning后,系统函数被自己的取代,不管是在自己还是系统调用中

《C专家编程》读书笔记_第3张图片

                                                                                      图 2   interpositioning 

         准则:不要让程序中的任何符号成为全局的,除非有意把它们作为程序的接口之一

          在ld程序中使用-m选项,让连接器产生一个报告,里面包含了被interpose的符号说明(链接器报告文件).

         当程序在scanf()和printf()中使用浮点数格式,但并不调用任何其他浮点数函数(浮点库)时,就有可能猜测错误(?).

四、数组与指针

     1、数组、指针的声明与定义

          1)声明:declaration; 定义: definition

              引用:声明相当于普通的声明,它所说明的并非自身,而是描述其他地方的创建的对象;定义相当于特殊的声明,它为对象分配内存;

          2)数组和指针的访问

              a. 数组的下标引用(图)

《C专家编程》读书笔记_第4张图片

                                                                               图 3 数组的下标引用

              b. 指针的引用(图)

《C专家编程》读书笔记_第5张图片

                                                                                     图 4  对指针的引用 

     2、数组、指针的基本区别

《C专家编程》读书笔记_第6张图片

                                                                                      图 5 数组与指针的区别 

     3、数组、指针的可交换性

          1)“表达式中的数组名”就是指针;

          2)C语言把数组下标作为指针的偏移量;

          3)“作为函数参数的数组名”等同于指针;

          4)其他所有情况中,定义和声明必须匹配;

     4、数组与指针的混合使用

          1)C语言中的多维数组

          2)使用指针创建和使用动态数组              

#include 
#include 

int size;
char *dynamic;
char *input[10];
scanf("%d", size);

dynamic = (char *)malloc(size);
dynamic[0] = 'a';

五、内存模型与运行时数据结构

    1.a,out:assembler output(汇编程序输出)缩写,汇编程序和链接编辑输出格式(老式BSD文档),链接器输出.

 2.运行时数据结构:堆栈,活动记录,数据,堆

  1)堆栈:为临时、局部变量分配空间;函数调用时保护现场;用作暂时存储区;

  2)过程活动记录:函数调用时用来保护现场的数据结构,存放于寄存器中时效率更高,相关函数setjmp()和longjmp(),类似catch/throw;

  3)关于多线程,为每个线程分配不同的堆栈,提高处理性能

《C专家编程》读书笔记_第7张图片

                                                                                        图 6    利器 -- 储备
* 寄存器窗口 -- 过程活动记录的内容放在寄存器中
* 保护模式 -- 段寄存器并不与偏移地址相加,而是为一个存放实际段地址的表提供索引
* Intel 80x86内存模型 -- 各处理器的地址空间并不一致,因为要保持兼容性
* 不同的段地址和偏移地址形成的指针可能指向同一个内存地址
* PC的内存模型:
    * huge large far near
    * small : 
    * large :
    * medium :
    * compact :
* 非标准关键字
    * __near
    * __far
    * __huge
    * 这些关键字修改的时它们右邻的项目,const和volatile类型修饰符所修改的是它们左边紧邻的项目
* 内存扩展方案expander和内存扩充方案extender
* 虚拟内存
    * 页是操作系统在磁盘和内存之间移来移去或进行保护的单位,一般为几K字节
    * 在磁盘中有一个特殊的“交换区”,用于保存从内存中换出的进程
* 两种类型的cache
    * 全写法cache
    * 写回法cache
    * 行是对cache进行访问的单位
    * 一个cache行内的数据称作块,保存来回移动于cache行和内存之间的字节数据;
* 进程的内部布局
    * 堆不能按名字直接访问,只能通过指针访问
* 管理内存的调用是:
    * malloc 和 free : 从堆中获得内存以及把内存返回给堆;
    * brk 和 sbrk : 调整数据段的大小至一个绝对值(通过某个增量)
    * 想获得以后能够返回给系统调用的内存时,使用mmap系统调用 映射/dev/zero文件
    * 需要返回这种内存时,可以使用munmap系统调用

内存泄漏


* 堆经常会出现的问题:
    * 释放或改写仍在使用的内存(称为“内存损坏”)
    * 未释放不再使用的内存(称为“内存泄漏”)

总线错误


* bus error(core dumped) 总线错误 和 segmentation fault(core dumped) 段错误(信息已转储)
    * 总线错误几乎都是由于未对齐的读或写引起的,被阻塞的组件就是地址总线, 对齐--数据项只能存储在地址是数据项大小的整数倍的内存位置上, 也可能由于引用一块物理上不存在的内存引起
    * 段错误是由于内存管理单元(负责支持虚拟内存的硬件)的异常所致,而该异常通常是由于解除引用一个未初始化或非法值的指针引起的
* 通常导致段错误的几个直接原因:
    * 解除引用一个包含非法值的指针;
    * 解除引用一个空指针(常常由于从系统程序中返回空指针,并未检查就使用)
    * 在未得到正确的权限时进行访问,例如,试图往一个只读的文本段存储值就会引起段错误
    * 用完了堆栈和堆空间(虚拟内存虽然巨大但绝非无限)
* 可能导致段错误的常见编程错误:
    * 坏指针值错误,在指针赋值之前就用它来引用内存;想库函数传送一个坏指针;对指针释放后再访问它的内容
    * 改写错误:越过数组边界写入数据;改写一些堆管理数据结构
    * 指针释放引起的错误
* 简单的说法:bus error意味着cpu对进程引用的一些做法不满,而segmentation error则是MMU对进程引用内存的一些情况发出抱怨

六、原型与类型 

   1、类型提升

       1)隐式类型转换

         * C隐式类型转换注意:
             * 隐式类型转换是语言的一种临机手段,起源于统一编译器处理数据的想法;

             * 即便不理睬缺省的类型转换,也可以;

             * 隐式类型转换在涉及原型的上下文显得非常重要;

         隐性类型转换在涉及原型的上下文中非常重要

       2)强制类型转换,涉及位扩展和位截除

   2、函数原型浅析

       一种新的函数声明机制,原因在于C函数声明可以不指定参数(不定参数?),而在原型中指定参数有利于形参和实参的参数检查;

   3、用C语言实现有限状态机(实践)

你可能感兴趣的:(出发,C/C++)