广州网站设计十年乐云seo,推广策略,大宗贸易采购平台,搜索引擎网络排名前言 从多个.c文件到达一个可执行文件的四步: 预处理–编译–汇编–链接
预处理 预处理过程就是预处理器处理这些预处理指令(要不然编译器完全不认识),最终会生成 main.i的文件 主要做的事情有如下几点:
展开头文件展开宏条件编译删除注释添加行号等信息保留…前言 从多个.c文件到达一个可执行文件的四步: 预处理–编译–汇编–链接
预处理 预处理过程就是预处理器处理这些预处理指令(要不然编译器完全不认识),最终会生成 main.i的文件 主要做的事情有如下几点:
展开头文件展开宏条件编译删除注释添加行号等信息保留parama预处理指令
头文件展开—#include指令 #include sdtio.h 和 #include “stdio.h” 对于 搜索顺序为 通过GCC参数gcc-I指定的目录注大写的I 让我们自由指定的。通过环境变量CINCLUDEPATH指定的目录。GCC的内定目录。 对于 的搜索时顺序项目当前目录(此时也可以用…/LED/led.h方式去搜索)通过GCC参数gcc-I指定的目录。通过环境变量CINCLUDEPATH指定的目录。GCC的内定目录 为什么把声明放在头文件里 提供一个接口 方便其他文件通过声明调用对应的函数当我们的led.c 包含了 led.h的时候 也方便编译器做类型的检查 头文件多次包含会增加可执行文件的体积吗 只要是使用了类似#pragma once 或者#ifndef 多次包含是不会的增加可执行文件的体积的 同样的要注意:声明不会增加可执行文件的体积 宏展开#define 宏指令 宏定义最小 #define MIN(x,y) ((x) (y) ? (y) : (x)) 因为宏只是做了一个替换所以对于如下代码 #include stdio.h#define MIN(x,y) ((x) (y) ? (y) : (x))//因为宏只是做了一个替换所以对于如下代码int main(){int a 2 ;int b 5;int c MIN(a,b);printf(c %d a %d b %d\r\n,c,a,b); // a竟然等于4}在这里可以用GNU C语法中的一些小技巧操作 #define MIN(x,y) ({\typeof(x) _x (x);\typeof(y) _y (y);\_x _y ? _x : _y;})定义一个很大的常数的时候 #define MAX_LONG (100001000010000)UL //指定类型##连接符 高端用法 看了好多代码都用这个 但是分析起来乱乱的大概就是把两个字母连接到一起 #define contact(x,y) (x##y)int bc 50;printf(bc %d\r\n, contact(b,c));offset_of与container_of 之前写结构体的时候写过,权当复习一下#define offset_of(type, member) ((size_t)(((type *)0)-member))
#define container_of(type,member,ptr) (type *)((size_t) ptr - offset_of(type,member))
struct student {int height;char * name;
};
int main()
{struct student stu;stu.height 50;stu.name 123456;char ** tmp_name stu.name;struct student* s stu;printf(%p %p %ld\r\n,s, tmp_name,offset_of(struct student,height));struct student *new_s container_of(struct student,name,tmp_name);new_s-height 60;printf(%d\r\n,stu.height);
}宏为什么要用 do {} while(0) 如果去看linux源码也好还是RTOS等的代码也好 会有很多时候用到do_while(0) 它的作用是什么呢 假设我们定义了 #define MACRO() foo(); bar() 此时我们写了这样的伪代码 if (condition) MACRO(); else baz(); // 宏展开后和我们想要的就完全不一样了 直接就出错了 // do {}while(0)可以保证宏作为一个整体执行 此时就可以定义一些局部变量
条件编译 #ifdef等指令 条件编译指令 正常用的比较多的就是 #ifndef #define #endif这几个连用 也有 #defined(VAR_X)之类的 #error指令 如果发生错误直接中断编译过程
#pragma 指令 pragma pack([n])指示结构体和联合成员的对齐方式。pragma message(“string”)在编译信息输出窗口打印自己的文 本信息。pragma warning有选择地改变编译器的警告信息行为。pragma once在头文件中添加这条指令可以防止头文件多次 编译。
编译 真要讲编译我也是不配讲的 就我们知道这是在干嘛就行了 编译就是把.c文件变成汇编文件的过程
编译过程的6步 词法分析 / 语法分析 / 语义分析 / 中间代码生成 / 汇编代码生成 / 目标代码生成 语法错误: stynax error: 缺少分号 / {}没扩住 /语义错误: 类型不匹配 未定义的变量 最终的结果就是生成.S文件 gcc的优化等级 gcc -O 可以参考 https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#Optimize-Options交叉编译 嵌入式开发板一般是ARM架构 然后PC是x86架构 通过交叉编译器进行程序的编译
汇编 汇编就是把汇编代码编程机器码 也就是比较熟悉的 xx.o文件了 汇编过程最终会生成以零地址为链接起始地址的可重定位目标文件
链接 把.o 文件进行组装 需要重定位 因为所有的.o文件的开头都是以0地址开头的 链接主要分为3个过程:分段组装、符号决议和重定位 分段组装 不太好讲 基本就是通过一个脚本把多个文件按照段组合到一起 符号决议 符号决议的核心就行 如果说变量/函数重名了怎么办 不允许同时存在两个相同的强符号 初始化的全局变量、函数名默认都是强符号未初始化的全局变量默认是弱符号 比如 // b.cint i; // 未初始化 是弱符号int main() {printf(%d\r\n,i); //i的值是20}// a.cint i 20;__attribute__关键字 可以把强符号强行转换为弱符号 attribute((weak))
使用弱符号的好处 自定义重名函数 这里在嵌入式里最常见的就是中断服务函数的弱定义了 当我们需要重新定义中断服务函数的时候 只需要保证名字很start.S的名字一致就行,链接的时候就知道链接到哪里了 检查该函数是否存在 // b.c #include stdio.h int global_k; char global_i; attribute((weak)) void func() { printf(“这被定义为弱符号了\r\n”); } int main() { printf(“%d\r\n”,global_k); if(func) func(); //调用的是强符号的函数 return 0; } // a.c #include stdio.h int global_k 20; int global_i; void func() { printf(“这被定义为强符号了 fun\r\n”); } 同样都是弱符号 谁体积大谁胜出重定位 因为要把不同的文件链接到一块 所以位置就会发生变化