`

【转】《C专家编程》读书笔记(2)

阅读更多

原文地址:http://blog.csdn.net/arthurkingios/archive/2007/05/10/1603715.aspx

***数组和指针是如何访问的***

 

标准规定赋值符必须用可修改的左值(为了与数组名区分,数组名也用于确定对象在内存中的位置,也是左值,但它不能作为赋值的对象)作为它左侧的操作数,而其右侧的操作数是地址的内容(右值)。

数组和指针的差别:
(1)数组只需将起始地址加上偏移地址,再解除引用既可取得数组中的各个元素;而指针需先将自身解除引用以获取所指向对象的地址,再解除引用才可取得所指向对象的内容;

(a)对数组下标的引用:

char a[9= "abcdefgh";
char c;
= a[i];
//编译器符号表具有一个地址9980
//运行时步骤1:取i的值,将它与9980相加
//运行时步骤2:取地址(9980+i)的内容
(b)对指针的引用:
char* p;
char c;
= *p;
//编译器符号表有一个符号p,它的地址是4624
//运行时步骤1:取地址4624的内容,得到5081
//运行时步骤2:取地址5081的内容

(c)定义为指针,但以数组方式引用:

char* p = "abcdefgh";
char c;
= p[i];
//编译器符号表具有一个p,地址为4624
//运行时步骤1:取地址4624的内容,得到5081
//运行时步骤2:取i的值,将它与5081相加
//运行时步骤3:取地址(5081+i)的内容

其实质是上述a、b访问方式的组合,只有在p原先定义为指针时这个方法才是正确的。
如果p被声明为extern char* p,而它原先的定义却是char p[10]的情形,既然把p声明为指针,那么不管p原先是定义为指针还是数组,都会按照c方式进行操作。当用p[i]这种方式提取一个声明的内容时,实际上得到的是一个字符。但按照上述的方法,编译器却把它当成是一个指针,把字符解释为地址显然是非法的。

(2)指针通常用于动态数据结构,而数组通常用于存储固定数目且数据类型相同的元素;
(3)指针通常指向匿名数据,而数组自身即为数据名。

***编译器的构成***

绝大多数编译器并不是一个单一的庞大程序,而是由预处理器(preprocessor)、语法和语义检查器(syntax and semantic checker)、代码生成器(code generator)、汇编程序(assembler)、优化器(optimizer)、链接器(linker)、驱动器程序(driver program)组成。 目标文件并不能直接执行,它首先需要载入到链接器中。链接器确认main函数为初始进入点,把符号引用(symbolic reference)绑定到内存地址,把所有的目标文件集中在一起,再加上库文件,从而产生可执行文件。 如果函数库的一份拷贝是可执行文件的物理组成部分,那么我们称之为静态链接;如果可执行文件只是包含了文件名,让载入器在运行时能够找到程序所需要的函数库,那么我们称之为动态链接。

***动态链接的相关知识点***

动态链接的优点:
(1)可执行文件小(运行速度稍慢);
(2)链接-编译阶段的时间短(部分工作被推迟到载入时);
(3)ABI(Application Binary Interface),即应用程序二进制接口,把程序与它们使用的特定的函数库版本分离开来,程序可以调用接口所承诺的服务,而不必担心这些功能是怎么提供的或者它们的底层实现是否改变。
(4)允许用户在运行时选择需要执行的函数库。

与位置无关的代码表示用这种方法产生的代码保证对于任何全局数据的访问都是通过额外的间接方法完成的。这使它很容易对数据进行重新定位,只要简单地修改全局偏移量表中的一个值即可。根据经验,对于函数库应始终使用与位置无关代码,尤其是对于共享库,因为每个使用共享库的进程一般会把它映射到不同的虚拟地址(尽管共享同一份物理拷贝)。 纯代码,也被称作纯可执行文件,是只包含代码(无静态或初始化过的数据)的文件。它之所以称为“纯”是因为它不必进行修改就能被其它特定进程执行,它从堆栈或其它段引用数据。如果生成与位置无关代码(意味着共享),你通常也希望它是纯代码。

***警惕Interpositioning***

Interpositioning就是通过编写与库函数同名的函数来取代该库函数的行为。但这种方式有一个十分隐蔽的bug:不仅你自己所进行的所有对该库函数的调用将被自己版本的函数调用所取代,而且所有调用该库函数的系统调用也将用你的函数取而代之。因此,应尽量避免使用Interpositioning。

***运行时数据结构***

UNIX中所有的输出文件都缺省地使用同一个名字a.out,在a.out中的第一个字是一个无条件跳转指令,以进入程序第一个真正的可执行指令。紧跟在后面的第二至第四个字节为“ELF”。现在绝大多数SVr4实现都采用这种称作ELF(Executable and Linking Format)的可执行文件。

在UNIX中,段(segment)表示一个二进制文件相关的内容块,section是ELF文件中的最小组织单位。一个段一般包含几个section。

a.out文件依次包含以下几部分内容:
(1)a.out神奇数字(用于跳转到第一个真正的可执行指令);
(2)a.out的其它内容;
(3)BSS段所需的大小(BSS段只保存没有值的变量,所以事实上它不需要保存这些变量的映像。运行时所需要的BSS段的大小记录在目标文件中);
(4)数据段(放置初始化后的全局和静态变量);
(5)文本段(放置可执行文件的指令)
备注:程序中的局部变量不进入a.out,它们在运行时创建。

***操作系统如何载入a.out文件中的内容***

段可以方便地映射到链接器在运行时可以直接载入的对象中。从本质上说,段在正在执行的程序中是一块内存区域。
进程的地址空间通常包括以下几部分(从低地址到高地址):
(1)未映射区域:
在典型情况下,它是从地址零开始的几K字节,用于捕捉使用空指针和小整型值的指针引用内存的情况;
(1)文本段:
文本段包含程序的指令。链接器把指令直接从文件拷贝到内存中,以后便再也不用管它;
(2)数据段:
数据段包含经过初始化的全局和静态变量以及它们的值。
(3)BSS段:
链接器从a.out中读出BSS段所需的大小,然后得到这个大小的内存块,紧跟在数据段之后。包括数据段和BSS段的整个区段通常统称为数据区。
(4)堆栈段:
用于保存局部变量、临时数据、传递到函数中的参数等。
备注:还需要堆(heap)空间,用于动态分配的内存。

***C语言怎样组织运行时数据结构***

运行时数据结构有以下几种:
(1)堆栈段:
运行时系统维护一个指针(常位于寄存器中),通常称为sp,用于提示堆栈当前的顶部位置。
堆栈段主要有三个用途:
(a)堆栈为函数内部声明的局部变量(即自动变量)提供存储空间;
(b)进行函数调用时,堆栈维护与此相关的一些维护性信息(即过程活动记录),包括函数调用地址,任何不适合装入寄存器的参数等;
(c)堆栈也可以被用作暂时存储区,如一些复杂算术表达式的结果可被压到堆栈中。
备注:除了递归调用,堆栈并非必需。
(2)过程活动记录:
过程活动记录的规范描述包括:局部变量、参数、静态链接(用于上层引用,C语言中不使用)、指向先前结构的指针、返回地址等。 绝大多数语言允许函数的嵌套定义,而静态链接就是一个指向它的外层函数的活动记录的指针。
静态链接与动态链接的区别:
静态链接(指向从词法上讲属于外层过程的活动记录,由编译时决定)
动态链接(在运行时指向最靠近自己的前一个过程调用的活动记录)

***auto和static关键字***
如果从函数中返回一个指向该函数局部自动变量的指针,将会出现“悬垂指针(dangling pointer)”(并不引用有用的东西,而是悬在地址空间中)。原因在于自动变量在堆栈中进行分配,而当函数结束后,变量不复存在,它所占用的堆栈空间被回收,可能在任何时候被覆盖。

***内存管理***

堆位于BSS段的上端(更高内存地址),其中的所有东西都是匿名的,只能通过指针间接访问。

总线错误几乎都是由于未对齐的读或写引起的。它之所以称为总线错误,是因为出现未对齐的内存访问请求时,被阻塞的组件就是地址总线。在现代的计算机架构中,数据对齐可极大地简化如Cache等硬件,迫使每个内存访问局限在一个Cache行或一个单独地页面内。例如,访问一个8字节的double数据时,地址只允许是8的整数倍。
一个会引起总线错误的小程序:

union
{
    
char a[10];
    
int i;
}
u;
int* p = (int*)&(u.a[1]);
*= 17;    //p中未对齐的地址会引起一个总线错误

段错误是由于内存管理单元(负责支持虚拟内存的硬件)的异常所致,而该异常通常是由于解除引用一个未初始化或非法值的指针引起的。如果指针引用一个并不位于尼的地址空间中的地址,操作系统便会对此进行干涉。
一个会引起段错误的小程序:

int *= 0;
*= 17;

一个极为常见的与释放内存有关的错误就是在for(p = start;p;p=p->next)这样的循环中迭代使用free(p)释放内存。正确的做法是:

struct node *p, *start, *tmp;
for(p = start;p;p = tmp)
{
tmp 
= p->next;
free(p);
}

ANSI C中流行的一种不良方法:调用函数和通过指针调用函数(或任意层次的指针间接引用)可以使用同一种语法。具体如下例所示:

int (*state[MAX_STATES])();    //声明函数指针数组
extern int a(), b(), c(), d();
int (*state[])() = { a, b, c, d };    //初始化
(*state[i])();    //通常的调用函数的方式
state[i]();       //通过指针调用函数
(****state[i])(); //任意层次的指针间接引用

 

分享到:
评论

相关推荐

    C专家编程读书笔记(上).pdf

    C专家编程读书笔记(上).pdf

    C 专家编程 适合有一定C基础的人看

    《C专家编程》是一本面向有一定C语言基础的读者的书籍,它的目标是帮助这些读者在C编程方面达到专家级别的水准。这本书由彼得·范·德·林登(Peter Van Der Linden)撰写,由徐波谋翻译,首次出版于2002年12月,由...

    谭浩强老师C语言程序设计PPT及笔记

    “谭浩强C程序设计和读书笔记以及PPT”这部分可能是谭浩强老师的个人授课笔记,其中可能包含了他对C语言更深层次的见解和独特的解题方法,也可能包括了课堂讲解的PPT,这些都为读者提供了丰富的学习材料。阅读这些...

    尚观培训linux许巍老师关于c语言的课程笔记和讲义

    【尚观培训Linux许巍老师关于C语言的课程笔记与讲义】是针对想学习或深化C语言理解的IT从业者及爱好者的一份宝贵资源。尚观是一家知名的IT培训机构,其提供的课程涵盖了广泛的IT领域,包括嵌入式Linux驱动开发。这份...

    《架构整洁之道》读书笔记.pdf

    《架构整洁之道》是软件开发领域的一本经典著作,由知名专家Robert C. Martin撰写。Martin先生不仅是cleancoders.com的联合创始人,还是Uncle Bob Consulting LLC的创始人,他在全球范围内提供软件开发咨询服务,...

    转嵌入式 Linux入门笔记

    这份"转嵌入式Linux入门笔记"提供了宝贵的资源,帮助初学者快速踏入这个领域。下面,我们将详细探讨嵌入式Linux的基本概念、重要性以及学习路径。 嵌入式Linux是指将Linux操作系统与硬件平台紧密结合,用于实现特定...

    python核心笔记pdf

    Python是一种高级、通用的编程语言,以其简洁明了的语法和强大的功能而受到广大程序员的...通过阅读这份笔记,读者不仅可以掌握Python的基本语法,还能了解到更高级的概念和技术,为成为Python开发专家打下坚实的基础。

    马士兵多线程训练营笔记

    在IT领域,多线程是并发编程中的一个关键概念,特别是在Java这样的多线程支持语言中。马士兵是一位知名的IT教育专家,他的多线程训练...在阅读和学习这些笔记时,结合实际的编程练习将有助于更好地理解和巩固所学知识。

    韦东山二期驱动笔记

    10. **案例分析**:韦东山的笔记很可能包含了一些实际设备的驱动编写案例,如串口、GPIO、SPI、I2C等常见接口的驱动编写,通过这些案例可以更好地理解驱动开发的流程和细节。 通过阅读和研究《韦东山二期驱动笔记》...

    韩顺平笔记 PHP笔记 资料难得 赶紧下载吧

    韩顺平老师的笔记中可能涵盖了这些基础知识的实例和讲解,通过阅读和实践,可以逐步提升PHP编程能力。此外,资料中的“3C3N导航-浓缩精华-双重搜索引擎.url”可能是提供其他学习资源的链接,帮助你在学习过程中找到...

    ARM嵌入式系统技术笔记-基于LPC2300上

    接着,书中会详细介绍LPC2300系列的硬件特性,包括GPIO(通用输入/输出)、定时器、串行通信接口(如UART、SPI、I2C)以及ADC(模数转换器)等。这些外设在实际应用中至关重要,了解它们的工作原理和编程方法是设计...

    C语言(谭浩强word版)和同步训练

    C语言是一种广泛应用于系统编程、应用编程、嵌入式开发等多个领域的高级编程语言,由Dennis Ritchie在20世纪70年代初为UNIX操作系统开发。它的语法简洁、灵活,且对硬件的控制能力强,因此深受程序员喜爱。谭浩强...

    精读特训笔记.zip

    本压缩包“精读特训笔记.zip”包含了关于精读学习方法和技巧的详细资料,旨在帮助用户提升阅读理解能力,尤其是对于专业文章和技术文档的理解。精读是一种深入理解文本的阅读策略,它要求读者不仅理解文字表面意思,...

    开发专家 Visual C 开发入行真功夫光盘

    标题中的“开发专家 Visual C 开发入行真功夫光盘”表明这是一份与Visual C++编程相关的学习资源,可能是包含教程、实例代码、练习题等的光盘内容。Visual C++是微软公司开发的一款集成开发环境(IDE),主要用于...

    C语言学习课件(北邮版)

    这个课件可能包含了一系列的讲座笔记、课后练习、实例代码和解题思路,由北京邮电大学的专家精心编撰。北京邮电大学作为中国知名的高等学府,其计算机科学教育具有较高的权威性,因此,这个课件的质量可以得到保证。...

    一步一步学VB2005数据库编程

    "WROX-Expert_One_On_One_Visual_Basic_2005_Database_Programmingc.pdf"看起来是一本完整的电子书,由著名的技术出版商Wrox出版,专门探讨VB2005的数据库编程,包含专家级别的指导和实践案例。"E书说明.txt"可能是...

    FPGA学习笔记 FPGA

    FPGA(Field-Programmable Gate Array)是一种可编程逻辑器件,允许用户根据需求自定义数字电路。在本文中,我们将探讨FPGA的学习路径、主要工具以及相关资源,旨在为初学者提供指导。 首先,学习FPGA需要掌握硬件...

    oracle笔记 (PL-SQL)

    Oracle笔记(PL-SQL)是关于数据库管理和编程的一个重要主题,主要聚焦于Oracle数据库系统中的结构化查询语言(SQL)的扩展——PL/SQL。PL/SQL是一种过程化语言,结合了SQL的功能,使得开发者能够编写复杂的数据库应用...

    STM32自学笔记 2012.rar

    STM32是一款基于ARM ...通过阅读《STM32自学笔记》,读者将能全面了解STM32的基本概念、开发流程和实战技巧,为成为STM32领域的专家奠定坚实基础。同时,结合实际项目练习,能够更好地巩固理论知识,提高动手能力。

Global site tag (gtag.js) - Google Analytics