编程范式(斯坦福大学)学习笔记《十二》
本节课讲解的主要是预处理器,编译和连接的异同。
#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函数。
编程范式(斯坦福大学)学习笔记《十二》相关推荐
- 编程范式(斯坦福大学)学习笔记《二》
斯坦福大学开放课程:编程范式学习笔记<二> 本课讲述了C/C++关于int,float等数据的底层表示,以及赋值操作所进行的处理.本节内容比较简单,应该属于组成原理的基础知识,各种码的表示 ...
- 编程范式(斯坦福大学)学习笔记《四》
斯坦福大学开放课程--编程范式(四) 综述 本节课的主要内容是关于泛型数据的拷贝,虽然是使用C语言实现,并且没有用到C++中的模板这种泛型编程技术,但是效果却非常好.本节内容紧接上节所将的字节位拷贝的 ...
- 编程范式(斯坦福大学)学习笔记《十一》
上次的课中都是使用C代码生成汇编代码,这次课中将使用C++代码生成汇编代码,对比结果发现这两种语言最终生成的代码形式很像. C语言版本: void foo() {int x;int y;x=11;y= ...
- Netty学习笔记二网络编程
Netty学习笔记二 二. 网络编程 1. 阻塞模式 阻塞主要表现为: 连接时阻塞 读取数据时阻塞 缺点: 阻塞单线程在没有连接时会阻塞等待连接的到达,连接到了以后,要进行读取数据,如果没有数据,还要 ...
- NVIDIA可编程推理加速器TensorRT学习笔记(二)——实操
NVIDIA可编程推理加速器TensorRT学习笔记(二)--实操 TensorRT 是 NVIDIA 自家的高性能推理库,其 Getting Started 列出了各资料入口,如下: 本文基于博 ...
- css中怎么加入立体模型,CSS学习笔记二:css 画立体图形
继上一次学了如何去运用css画平面图形,这一次学如何去画正方体,从2D向着3D学习,虽然有点满,但总是一个过程,一点一点积累,然后记录起来. Transfrom3D 在这一次中运用到了一下几种属性: ...
- wxpython应用程序对象与顶级窗口_wxPython学习笔记(二)
如何创建和使用一个应用程序对象? 任何wxPython应用程序都需要一个应用程序对象.这个应用程序对象必须是类wx.App或其定制的子类的一个实例.应用程序对象的主要目的是管理幕后的主事件循环. 父类 ...
- NumPy学习笔记 二
NumPy学习笔记 二 <NumPy学习笔记>系列将记录学习NumPy过程中的动手笔记,前期的参考书是<Python数据分析基础教程 NumPy学习指南>第二版.<数学分 ...
- qml学习笔记(二):可视化元素基类Item详解(上半场anchors等等)
原博主博客地址:http://blog.csdn.net/qq21497936 本文章博客地址:http://blog.csdn.net/qq21497936/article/details/7851 ...
最新文章
- 两个摄像头是如何将照片拼接在一起的
- 前嗅ForeSpider教程:网站登录配置
- 2983:谁是你的潜在朋友
- [工具]-文件明文导出工具
- 怎样理解Linux的文件系统
- JAVA语言异常,Java语言中的异常
- Vue (响应式原理-模拟-4-Dep)
- IOS精品源码,仿探探UIButton封装iOS提示弹框迅速引导页自定义导航栏
- 7-3 奇偶分家 (10 分)
- android 生成长截图,【UNIAPP截长图】方案之一:滚动截屏 Android
- 人工智能第一讲:人工智能概论
- 用selenium模拟QQ空间登录
- OpenWrt 18.06.1的ss-redir, 以及在乐视超4 X40上看Youtube
- UiBot RPA文字转拼音
- SimulinkSTM32开发(一)开发环境搭建
- java中奇数魔方,算法-奇数阶魔方
- Fairplay之 generating FairPlay content key request on iOS after 14.6
- 电力电子技术笔记(5)——其它新型电力电子器件、功率集成电路
- oracle中 ''dual'' 的含义
- Unity3D 批量修改模型名称
热门文章
- 什么是触摸一体机?触摸一体机给用户带来全新体验。
- 解决MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk
- C语言中使用‘scanf’输入与‘printf’输出的几个例子
- 【两万字】面试128题汇总(含超详细答案)
- Games104现代游戏引擎入门-lecture16游戏引擎的Gameplay玩法系统基础_AI Basic
- MOS管为什么会被静电击穿?
- Unity对接应用宝SDK(YSDK)
- 封装Vue倒计时组件vuecountdown(详细教程)
- CSAPP 计算机漫游
- Medicine in Microecology:Nanopore三代测序人类肠道病毒组的方法