程序环境和预处理
一.程序环境
翻译环境
在标准C中我们自己编写的c语言代码在我们的翻译环境中被给翻译成了机器可以识别的机器码也可以说是一种二进制语言。
程序的执行环境
顾名思义就是我们生成的机器可以识别的内容,机器在这个环境中识别我们的机器码。
二.C语言程序的编译和链接
编译链接的详解
编译和链接在一起生成可执行程序的时候大致的一个过程。
链接器把目标文件链接在一起,并且我们的链接器会去主动的获取这个C语言标准中在链接库中的函数。
编译内容比较多和细致
1.编译+链接
编译: 预编译(预处理)+ 编译 + 汇编
2.链接:
1.合并段表
2.合并符号表
1.一般函数发生错误都是在链接的过程中,因为这个时候我们才回去使用函数,如果被调用的函数未定义,就可以在链接过程中发现错误。
运行环境
1.程序必须进入内存
操作系统环境:通过操作系统完成这个过程
在独立的环境下:通过手动完成这个过程
2.程序执行,开始就调用main()函数
3.内存在程序运行中分成了
----- 1.运行时堆栈—局部变量函数参数
----- 2.静态内存—static修饰。
4.程序结束,正常结束main有可能是意外终止。
三.预处理
预定义符号的介绍
C语言标准内置的内容
__ FILE__进行编译源文件的位置
__ LINE__文件当前的行号
__ DATE__文件被编译的日期
__ TIME__文件被编译的时间
__ STDC__判断编译器是否服从标准C(ANSI C)数值就是1,否则就是没有定义这个内置符号
#define
1.#define定义标识符
格式: #define name stuff
stuff:类型,常量,字符串,语句
使用:
多种语句的情况
1.关键字的名称简化
2.语句内容的简化
3.续行符在#define中的作用
补充:在#define定义常量的时候不需要在常量后面加上;
#define a1 100;
#definr a2 100
选择第二种情况只希望a2代表数值!
#define定义宏
#define机制规定,允许把参数替换到文本中。这样的实现就称为宏或者说定义宏。
基本格式:#define name(preame_list)stuff
1.list是一个一个用逗号隔开的符号表,他们可能出现在stuff中
2.宏的参数需要括号
3.括号中有可能是一个语句。
4.语句整体需要带上括号。
注意:name必须与()相邻否则参数列表就会被解释成为stuff中的内容。
#define的替换规则
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先
被替换.- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上
述处理过程。
注意:1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
预处理操作符#和##的介绍
基础内容
1.#可以把宏参数变成对应的字符串
2.##的使用
字符串变量名称的操作
##可以把位于它两边的符号合成一个符号。
它允许宏定义从分离的文本片段创建标识符。
带有副作用的宏参数
宏和函数的比较
优点:
1.调用函数空间执行我们的算法(小型的计算工作),有可能说我们调用空间和返回数值要比真正执行算法的时间要长.我们可以把对应的算法写在我们的宏里面,减少打开函数和返回的过程,所有说宏在速度上是比函数要快的.
2.宏没有类型检查,函数有类型检查.类型检查是规定与我们自己决定的类型.所有函数只可以在类型合适的情况下去使用,但是我们的。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。
缺点
1.每一次使用我们的宏都会插入一份代码到我们的程序中,除非宏的内容比较短,否则很有可能大幅度的增加程序的长度。
2.宏是没有办法去调试的看不见执行的过程。
3.宏是类型无关所以没有那么严谨。
4.宏有可能带来运算符优先级产生的问题。
#define 和函数之间的类比
1.代码长度:
每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长。
函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码
2.执行速度:
比较快
存在函数调用和返回的const相对慢一些
3.操作符的优先级
宏的求值在上下文环境中,在没有括号的情况下算数是符号优先级会产生不可预计的后果,一定要在宏书写的过程中多加括号。
函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测
4.带有副作用的参数
宏的参数可能会多次调用如果这个参数有副作用,产生不可预结果。
函数参数只在传参的时候求值一次,结果更容易控制。
5.参数类型
宏没有类型检查,只要参数操作合法就可以传递任何参数。
函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是相同的。
6.调试
宏是不方便的
函数比较方便
7递归
宏是不可以递归
函数是可以递归的。
补充:
命名约定
把宏名全部大写
函数名称使用驼峰命名
区分函数和宏
undef
//使用条件现在有的这个名称需要被重新定义。
命令行定义
许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。
例如:当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大些,我们需要一个数组能够大些。
总结编译一个程序的不同版本的时候我们使用这个特性
操作:gcc这样的命令行的情况下,可以在命令行中输入gcc test.c -D sz=10 -o test
这样就可以指定数组大小,在不同的情况下可以生成不同的可执行程序。
条件编译
判断是否编译
在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。
1.单分支
#if 常量表达式
...
#define __DEBUG__ 1
#if __DEBUG__
可执行语句
#endif
#endif的作用是结束条件编译过程。
2.多分支
#if()
----
#elif()
-----
#else
-----
#end
判断是否定义
//symbol 被定义名称
//注意:endif也是判断是否定义的结尾需要的
1.判断有没有定义。
#if defined(symbol)
#ifdef symbol
2.判断没有定义
#if !define(symbol)
#ifndef symbol
预处理指令#include和嵌套文件包含
头文件被包涵的方法
#include<>
1.直接在标准库中查找文件。
#include""
2.查找策略:先在源文件所在目录下查找,如果该头文件未找到,再去标准库中查找文件。
总结:标准库中使用<>
自己定义的头文件使用""
这样不容易混乱,而且查找库函数使用""是可以的但是速度会比使用<>慢
重复包涵的情况
1.一个#define定义的被重复定义,或许你这个.c中需要定义一个常量,但是在你包涵的.h中已经被定义成一个宏。我们可以去判断是否被定义:
#if defined(symbol) or#ifdef symbol
#endif
2.或者在.h中使用#pragma防止重复包含。
避免头文件的重复引用!
四:两个宏的题目
写一个宏,计算结构体中某变量相对于首地址的偏移,并给出说明
考察:offsetof宏的实现
看一下offfsetof什么样的:头文件是stddef.h
1.如果说结构体的首地址在0的位置处那么成员变量的地址转化为size_t(不同类型的地址需要强制类型转换)就是偏移量!
交换奇偶位
作业内容
写一个宏,可以将一个整数的二进制位的奇数位和偶数位交换。
一个整数是32位我们先考虑8位的