OSpre——C
1. 源代码到可执行文件
- 预处理->编译->汇编->链接
1.1 预处理——处理#
gcc -E hello.c -o hello.i ## -E选项
- 预处理的工作内容:处理
#开头的预处理指令- 删除
#define,展开宏定义 - 处理条件编译指令,
#if,ifdef - 处理
#include预处理指令,将包含的文件内容插入到该预处理指令的位置
- 删除
1.2 编译——预处理文件生成汇编代码文件
gcc -S hello.i -o hello.s # -S参数
1.3 汇编——汇编代码转换为机器代码
gcc -c hello.s -o hello.o # -c参数
1.4 链接
把多个目标文件的代码段放在一起、数据段放在一起,以及库函数等形成可执行文件。
2. C语言中变量存储类别
2.1 存储期
2.1.1对象与标识符
C语言中对象是连续的一片内存空间,具有起始地址与大小两个属性。标识符是我们用来访问修改对象的字符串(变量名)
例如
int a = 5; // &a = 0x1000
我们声明了一个int类型,起始地址为0x1000,占用内存大小为4字节的整型变量,变量的标识符为a。
2.2.2 存储期——变量在内存中的生命周期
静态存储期static:
- 使用static关键字定义的变量
static int a - 在函数外定义的变量(全局变量)
若对象具有静态存储期,则在程序运行期间一直存在。并且对象的属性不变,即对象的起始地址和所占用的内存空间大小不会变化,不初始化自动初始化为0
- 使用static关键字定义的变量
自动存储期auto
不使用static关键字定义的变量(例如局部变量)
auto int a程序执行到该变量声明的时候会创建变量对应的对象,在执行到该变量作用域结束后释放对象,不进行初始化则初始值不确定。
- 如在函数中生命的局部变量,在他的一次调用中的作用域中具有固定的值和地址属性,不同的调用地址属性可能不同
2.2 作用域——标识符在程序中可以被使用的区域
块作用域:块(
block)是用花括号括起来的代码区域。定义在块中的变量具有块作用域。块作用域变量的可见范围是从定义处到包含该定义的块的结尾(右花括号)。(对应着局部变量)int foo(int a, int b) { int c; //c 作用域开始 { c=1; int d; // d作用域开始 d=0; } // d作用域结束 } // abc作用域结束注:函数的形式参数块作用域属于函数体块
文件作用域:在函数外定义的变量,从定义处到文件末尾均可见
全局变量:文件作用域,静态存储期
局部变量:块作用域,自动存储期
2.3 链接
链接属性:该变量是否可在别的文件中被使用
内部链接:内部链接变量只能在定义他的文件中使用
使用
static关键字声明的全局变量static int a = 1;//static 修饰全局变量
外部链接:可以在所有文件中使用
全局变量默认是外部链接的
int b = 2//默认全局变量
无链接:变量没有链接属性
在函数中定义的变量,即只有全局变量有链接属性,局部变量没有
void foo(int c) { int d; }
若要在外部文件中使用具有外部链接属性的变量,需要extern关键字进行引用式声明
定义式声明:创建对象
引用式声明:引用其他地方变量
extern int foo;
2.4 总结
| 存储类别说明符 | 变量声明位置 | 存储期 | 链接 | 备注 |
|---|---|---|---|---|
auto | 函数内 | 自动存储期 | 无链接 | auto 关键字可以省略 |
static | 函数内 | 静态存储期 | 无链接 | |
static | 函数外 | 静态存储期 | 内部链接 | |
| 无 | 函数外 | 静态存储期 | 外部链接 | 若要在别的文件中使用这种变量需要使用 extern 关键字进行引用式声明 |
extern | 函数内 | 静态存储期 | 引用的变量需要具有外部链接 | 引用式声明,不会创建对象 |
extern | 函数外 | 静态存储期 | 引用的变量需要具有外部链接 | 引用式声明,不会创建对象 |
例,有以下三个C语言文件
/* main.c */
static int v;
extern int func(int value);
int main() {
int value = 1;
v = func(value);
{
extern int value; // 在块中引用变量 value value的作用域为当前代码块 value = 2
v += func(value);
}
printf("%d\n", v);
return 0;
}
/* value.c */
int value = 2;
/* func.c */
int func(int value) {
static int x = 1; //static类型 静态存储期 会保留前一次调用后x的值,不会进行重新初始化
int y = 1; // auto类型 每次调用重新初始化
x += value;
y += value;
return x + y;
}
main.c中引用了value.c中的全局变量和func.c中的函数,编译他们需要同时进行编译
gcc main.c value.c func.c
./a.out
输出结果为11。
3. 函数的存储类别
默认函数为外部链接
使用别的文件中的函数,使用
extern关键字进行声明extern int bar(int a)函数内部链接:在函数定义前加上static关键字
4. 预处理指令
4.1 宏定义
变量式宏定义
#define N 20函数式宏定义
#define MAX(a,b) ((a) > (b) ? (a) : (b))
预处理器发现程序中的宏后,会用宏等价的替换文本进行替换(只是字符串层面的替换),直到不包含宏为止。
- 预处理器只负责对宏定义进行形式上的替换,函数式宏定义的参数没有类型,不做参数检查,即如果参数发生类型错误,在预处理阶段不会报错,在编译阶段报错
4.2 在MOS内核中推荐的函数宏定义形式
#define MACRO_NAME(para1, para2)\
do {\
express1;\
express2;\
}while(0)
- 注:在函数宏定义中每一行后添加的,其作用就相当于换行,防止一些离谱的错误
4.3 宏定义运算符
4.3.1 宏参数创建字符串 #运算符
#是预处理运算符,用于创建字符串,#会把传入的参数自动合并为用双引号括起来的字符串,参数中的多个连续空格会被替换为一个空格。
#define toStr(s) #s
printf(toStr(hello world));
经过#预处理后得到
printf("hello world!");
若#在双引号中,需要在#外再加双引号使其发挥作用,例如
#define PSQR(x) printf(" The square of " #x " is %d",((x)*(x)))
int y = 5;
PSQR(y);
// 输出:the square of y is 25
4.3.2 预处理器粘合剂 ##运算符
##运算符的作用是将前后两个预处理符号连接成一个预处理符号
#define CONCAT(a, b) a##b
// CONCAT(con, cat) 展开为 concat
4.3.3 变参宏:...和__VA_ARGS__
- 函数的宏定义的参数列表中使用…表示可变参数,在宏定义中可变参数的部分用__VA_ARGS__表示
#define showlist(...) printf(#__VA_ARGS__)
showlist(The first, second, and third items.);
预处理后结果为
printf("The first, second, and third items.");
若##运算符用在__VA_ARGS__前面,当它为空参数时,##运算符会把他前面的,吃掉
- 变参宏编写打印函数用于内核调试
#define DEBUGP(format, ...) printf(format, ## __VA_ARGS__) // DEBUGP("info no. %d", 1) 会展开为 printf("info no. %d", 1) // DEBUGP("info") 会展开为 printf("info"),注意展开式中的宏定义中的 format 后的逗号没有了。
4.3.4 关于define
undef:#undef <macro>取消对宏的定义,可以对<macro>赋新值。若之前没有定义过<macro>则会被忽略#include#include<name.h>在标准包含目录中查找该文件#include"name.h"现在引用该头文件的目录下查找,然后查找标准包含目录- 注:使用<>的一般是官方头文件,““一般是自行定义的头文件
ifndef:if not define常见于头文件的编写中,用于避免头文件的内容被重复包含。
//例如stdio.h #ifndef _STDIO_H #define _STDIO_H ... #endif这个规范要应用于自己的编写中
4.3.5 typedef 与 #define的区别
区别在于,#define 是单纯的字符串替换,在预处理阶段完成,没有作用域。而 typedef 是给类型一个别名,在编译阶段完成,它有自己的作用域。 typedef 一般用来定义类型的别名,定义与平台无关的数据类型,与 struct 的结合使用等。常见的 typedef 别名风格是以 _t 结尾,如 integer_t 或者 ptr_t。
#define出的类型宏可以被其他说明符修饰,但typedef定义的别名本身就是一个类型(类型说明符),所以在声明中不能和unsigned等其他类型说明符一起出现:
#define INTEGER int;
unsigned INTEGER n; //没问题
typedef int integer_t;
unsigned integer_t n; //错误,不能在 integer_t 前面添加 unsigned
typedef定义的类型名用来连续声明几个变量时,能够保证定义的所有变量均为同一类型,而#define则无法保证。
#define PTR_INT int *
PTR_INT p1, p2; //p1、p2 类型不相同,宏展开后变为 int *p1, p2;
typedef int * ptr_int_t;
ptr_int_t p1, p2; //p1、p2 类型相同,它们都是指向 int 类型的指针。