本节课讲解的主要是预处理器,编译和连接的异同。


#define有两个功能:一个是定义常量,一个是作为宏。

//定义常量
#define w 40
#define h 80
#define pere 2*(w+h)
//宏
#define MAX(a,b) (a>b?a:b)
MAX(10,40)//预处理替换为(10>40?10:40)//int max = (fib(100) > fract(4000)) ? fib(100) : fract(4000);展开替换后的结果,编译器不会保留中间结果,函数被调用了两次,尤其是大规模的函数会导致性能下降
int max = MAX(fib(100, fract(4000)));//int larger = ((m++) > (n++)) ? (m++) : (n++);//一共自增了3次
int larger = MAX(m++, n++);//最终会对大的两次自增,小的一次自增

注意:

1.这种宏定义市运行速度比函数快,而且不需要管参数的类型,例如:

MAX(40.2,"Hello");//预处理阶段没问题,但是编译阶段会报错。

2.在预处理阶段进行的都只是文本替换,结果作为数据传到下一阶段,预处理阶段并不进行类型检查,文本替换产生的问题会在之后的编译阶段进行识别。

3.为了避免define在预处理时不进行类型检查的缺点,应用static const定义全局变量。


//最好将重复代码写成函数或者是小段的宏,便于替换。
#define NthElemAddr(base, elemSize, index) ((char*)base + index * elemSize)
void* VectorNth(vector *v, int position)
{assert(position > 0)assert(position < v->logLength)return NthElemAddr(Vector->Elem, Vector->Size, positon);
} 

上面的define返回的是char*类型,而VectorNth函数是void*类型会出错吗?

没有问题。因为void*可以接受任何类型指针,就是所谓的上转型(upcasting),将一个更具体的指针转换成一个类型更泛化的指针。编译器知道这种类型转换并不会带来风险。如果进行下转型(downcasting),就告诉编译器,我现在有一个类型更加泛化的指针,我知道此指针具体类型是什么,但是如果涉及引用就想要进行强制转换。


#assert宏

#ifdef NDEBUG    //是关于某个define是否存在的判断,如果定义了NDEDUG,那么程序中所有的assert都会替换成空操作语句 #define assert(cond) (void)0    //将数字0强制转换为void类型,不要把这个0用在任何地方,也不许被赋值,作为一个空操作(nop),在?和:中间占位。虽然看上去是一条语句,但是它不会被编译成任何一条汇编指令
#else#define assert(cond) \(cond) ? ((void)0) : fprintf(stderr,"..文件名..行号等.."),exit(0);
#endif

#include

#include <stdio.h>
#include "vector.h"

1.使用尖括号:认为是系统文件,应该是编译器提供的,预处理器可以通过默认路径找到这些文件。/usr/bin/include和/usr/include中查找。

2.使用双引号:编译器会假设这是用户编写的.h文件,会默认从当前工作目录查找该头文件。通过makefile可以设定一些选项告诉编译器从哪里寻找这些包含的头文件。

3.#include指令也是查找并替换。用文件的内容替换掉该行#include指令。对于#include的处理是递归的,如果#include的指定文件本身还包含#include行,那么预处理器会一直深入下去直到最底层,层层替换直到生成不包含#include和#define的文本流。所以预处理后的文本流中,所有的#include和#define都被移除了。


gcc xxx -c vector.c

-c表示编译源文件,但是不要生成可执行文件。编译阶段之后就停止,只生成.o文件,不进行链接。

gcc xxx -E vector.c

-E表示只进行预处理,然后将结果输出,但是不进行后续阶段的操作。在生成的文件中前半部分都是导入的其他代码,在快结尾部分是自己的代码。


避免循环包含头文件,预处理器也不会让某个头文件被包含两次:

#ifndef _vector_h_
#define _vector_h_//列出所有vector.h中的原型
#endif

注意:

所有的.h文件都是定义某些原型的,不产生代码。好比定义某个结构类型,但是不会产生该结构体相应的代码。而且在.h文件中也不能申请任何存储空间,除非定义共享全局变量(很少使用)。但是.c和.cpp文件不同,它们定义了全局变量,全局函数和类方法等,都要被翻译成机器码(一系列01串),机器码可看作是汇编指令。包含.c和.cpp可认为是重复定义函数。声明一个函数和定义一个函数是不同的,对于函数实现而言,编译阶段会生成相应的代码,对于函数声明却不会产生任何代码。


1.vector.c文件包含了a.h,b.h,c.h文件。经预处理后去掉了#define和#include头。再经过编译得到.o文件(内容是汇编代码)。然后再经过链接得到可执行文件。(链接阶段将所有相关的.o文件组织到一起,连接器尝试使用这些文件创建可执行文件。这阶段需要有一个main函数,连接器才知道从哪里开始执行程序,对于每一个要被调用的函数都应该有定义,要求所有定义了的函数只被定义一次)。

2.如果在makefile或者gcc命令没有加额外选项的话,编译器会继续下面各个阶段并且生成可执行文件,默认情况下文件名为a.out。

3.链接会将.o文件和其他.o文件的各个部分进行混合重组(除了自己编写的模块,其他部分都是来自于编译器标准库或者是标准的.o代码)。


.o文件

#include<stdio.h>//printf
#include<stdlib.h>//malloc free
#include<assert.h>
int main(int argc, char* argv[])
{void* memory=malloc(400);assert(memory!=NULL);printf("Year!\n");free(memory);return 0;
}

注意:

1.#include<stdio.h>负责malloc,free,realloc,将其注释掉,就没有malloc,free,realloc的函数原型了。函数在执行到第7行时,也会将malloc推测成一个函数,并且推测函数有一个int参数,且返回一个int值,编译器并不会查看赋值函数来推测返回值是什么类型。因此编译器会对这行给出两条警告:第7行根据推测的函数类型,会认为是对一个指针赋值,而赋值的类型却是一个普通的整型。第10行编译器同样不知道free是什么,并推测它的原型,free的参数是void*,并且返回值是int,产生的内容和没有注释该行的.o文件完全一样。只是会报出三个错误,其中两条是说明缺失原型的,而一条是左值和右值类型不兼容,但是还是生成.o文件。当链接的时候链接器会忘掉这些警告,它不会记录有没有包含某个头文件,也不会记录编译时存在的警告。但是生成的.o文件和代码的语义是完全一致的,所以当链接并运行程序是没问题的。

2.负责printf,将这一行注释掉之后,预处理器生成的翻译单元中将不会有printf函数的声明。有的编译器会在编译阶段报错(函数未声明),gcc则不会报错。gcc会在编译时刻分析源程序,看看哪部分像是函数调用,会根据函数调用推测函数原型。编译器看到了调用printf,printf只有一个字符串作为参数,发出未找到printf函数原型的警告,但是不会停下来,还是继续生成.o文件。gcc推测一个函数原型时,将返回类型推测为int。如果还有其他的printf函数,那么只能同样是只有一个字符串参数(推测出的原型,函数参数个数不可变)。推测出的函数原型会与实际的函数原型稍有区别,但是生成的.o文件实际上完全没变(因为.h头文件只是包含结构的定义以及一些原型,对头文件来说不会产生任何汇编代码,头文件的用处只是告诉编译器一些规则,让编译器判定程序的语法正确与错误)。ld命令用来链接,链接命令会根据编译过程中出现的警告查找标准库,printf对应的代码就在标准库中,因此在链接阶段会被加进来,虽然在链接阶段之前并没有见过printf的原型。因此include并不能保证相应的函数实现在连接时可用,如果某个函数定义在了标准库中,那么在链接时就可以被加进来,而无论我们是否声明了函数原型。

3.如果将前两条include都注释掉,那么会产生4条警告,但是依然会生成.o文件,并且会链接生成a.out文件并执行它。其实头文件做的全部事情就是告诉编译器有哪些函数原型。但是在.h文件中并没有说明这些函数的代码在哪里,链接阶段则负责去标准库中寻找这些代码,而malloc,free,printf正是在标准库中。只要被调用的函数在标准库中存在,那么无论编译时有没有警告,生成的.o文件都会没有区别(包含原先代码的语义),因为在链接的时候可以用到标准库的代码,并将调用到的函数的代码加到.o文件集合中,因此会在.o文件中出现相应标号的函数,生成可执行文件。

4.#include <assert.h>注释掉之后编译器遇到第8行,看到的只是assert这个符号,而不是宏替换后的代码,一次编译器猜测它是一个函数调用,会在.o文件中出现CALL<assert>,造成编译成功,但是链接失败了,原因是标准库中根本没有assert函数。

编程范式(斯坦福大学)学习笔记《十二》相关推荐

  1. 编程范式(斯坦福大学)学习笔记《二》

    斯坦福大学开放课程:编程范式学习笔记<二> 本课讲述了C/C++关于int,float等数据的底层表示,以及赋值操作所进行的处理.本节内容比较简单,应该属于组成原理的基础知识,各种码的表示 ...

  2. 编程范式(斯坦福大学)学习笔记《四》

    斯坦福大学开放课程--编程范式(四) 综述 本节课的主要内容是关于泛型数据的拷贝,虽然是使用C语言实现,并且没有用到C++中的模板这种泛型编程技术,但是效果却非常好.本节内容紧接上节所将的字节位拷贝的 ...

  3. 编程范式(斯坦福大学)学习笔记《十一》

    上次的课中都是使用C代码生成汇编代码,这次课中将使用C++代码生成汇编代码,对比结果发现这两种语言最终生成的代码形式很像. C语言版本: void foo() {int x;int y;x=11;y= ...

  4. Netty学习笔记二网络编程

    Netty学习笔记二 二. 网络编程 1. 阻塞模式 阻塞主要表现为: 连接时阻塞 读取数据时阻塞 缺点: 阻塞单线程在没有连接时会阻塞等待连接的到达,连接到了以后,要进行读取数据,如果没有数据,还要 ...

  5. NVIDIA可编程推理加速器TensorRT学习笔记(二)——实操

    NVIDIA可编程推理加速器TensorRT学习笔记(二)--实操 ​ TensorRT 是 NVIDIA 自家的高性能推理库,其 Getting Started 列出了各资料入口,如下: 本文基于博 ...

  6. css中怎么加入立体模型,CSS学习笔记二:css 画立体图形

    继上一次学了如何去运用css画平面图形,这一次学如何去画正方体,从2D向着3D学习,虽然有点满,但总是一个过程,一点一点积累,然后记录起来. Transfrom3D 在这一次中运用到了一下几种属性: ...

  7. wxpython应用程序对象与顶级窗口_wxPython学习笔记(二)

    如何创建和使用一个应用程序对象? 任何wxPython应用程序都需要一个应用程序对象.这个应用程序对象必须是类wx.App或其定制的子类的一个实例.应用程序对象的主要目的是管理幕后的主事件循环. 父类 ...

  8. NumPy学习笔记 二

    NumPy学习笔记 二 <NumPy学习笔记>系列将记录学习NumPy过程中的动手笔记,前期的参考书是<Python数据分析基础教程 NumPy学习指南>第二版.<数学分 ...

  9. qml学习笔记(二):可视化元素基类Item详解(上半场anchors等等)

    原博主博客地址:http://blog.csdn.net/qq21497936 本文章博客地址:http://blog.csdn.net/qq21497936/article/details/7851 ...

最新文章

  1. 两个摄像头是如何将照片拼接在一起的
  2. 前嗅ForeSpider教程:网站登录配置
  3. 2983:谁是你的潜在朋友
  4. [工具]-文件明文导出工具
  5. 怎样理解Linux的文件系统
  6. JAVA语言异常,Java语言中的异常
  7. Vue (响应式原理-模拟-4-Dep)
  8. IOS精品源码,仿探探UIButton封装iOS提示弹框迅速引导页自定义导航栏
  9. 7-3 奇偶分家 (10 分)
  10. android 生成长截图,【UNIAPP截长图】方案之一:滚动截屏 Android
  11. 人工智能第一讲:人工智能概论
  12. 用selenium模拟QQ空间登录
  13. OpenWrt 18.06.1的ss-redir, 以及在乐视超4 X40上看Youtube
  14. UiBot RPA文字转拼音
  15. SimulinkSTM32开发(一)开发环境搭建
  16. java中奇数魔方,算法-奇数阶魔方
  17. Fairplay之 generating FairPlay content key request on iOS after 14.6
  18. 电力电子技术笔记(5)——其它新型电力电子器件、功率集成电路
  19. oracle中 ''dual'' 的含义
  20. Unity3D 批量修改模型名称

热门文章

  1. 什么是触摸一体机?触摸一体机给用户带来全新体验。
  2. 解决MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk
  3. C语言中使用‘scanf’输入与‘printf’输出的几个例子
  4. 【两万字】面试128题汇总(含超详细答案)
  5. Games104现代游戏引擎入门-lecture16游戏引擎的Gameplay玩法系统基础_AI Basic
  6. MOS管为什么会被静电击穿?
  7. Unity对接应用宝SDK(YSDK)
  8. 封装Vue倒计时组件vuecountdown(详细教程)
  9. CSAPP 计算机漫游
  10. Medicine in Microecology:Nanopore三代测序人类肠道病毒组的方法