在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
编译过程简单介绍 : C语言的源文件 编译成 可运行文件须要四个步骤, 预处理 (Preprocessing) 扩展宏, 编译 (compilation) 得到汇编语言, 汇编 (assembly) 得到机器码, 连接 (linking) 得到可运行文件; -- 查看每一个步骤的编译细节 : "-E" 相应 预处理, "-S" 相应 编译, "-c" 相应 汇编, "-O" 相应 连接; -- 每一个步骤相应的工具 : 预处理器 (CPP - The C Preprogressor), 编译器 (cc1), 汇编器 (as), 连接器 (ld); -- 查看整体编译细节 : 使用 "-v" 參数, 能够查看整体编译细节;
octopus@octopus:~/test$ gcc -v main.c 使用内建 specs。 1. 预处理预处理命令 : 源程序中 以 "#" 开头的命令是 预处理命令, 如 "#include", "#define", "ifndef" 等; 预处理过程 : 预处理将 include 的文件插入到 源文件里, 展开 define 宏定义, 依据条件 编译代码;
/************************************************************************* > File Name: main.c > Author: octopus > Mail: octopus_work.163.com > Created Time: 2014年04月30日 星期三 17时31分08秒 ************************************************************************/ #include<stdio.h> #define NUM 5 int main(int argc, char **argv) { printf("Hello World ! num = %d \n", NUM); return 0; } 预处理结果 : 预处理 源程序 产生的结果会放到 ".i" 后缀的文件里, 默认情况下 ".i" 后缀文件是不写到磁盘中的, 假设加上 "-save-temps" 參数, 就会将全部的中间文件都保存到磁盘中; -- 分析以下的样例 : 使用 gcc -save-temps main.c 命令编译源程序, 全部的中间文件都会保留, main.i 是预处理结果, main.s 是编译结果, main.o 是汇编结果, a.out 是连接生成的可运行文件;
octopus@octopus:~/test$ ls main.c octopus@octopus:~/test$ gcc -save-temps main.c octopus@octopus:~/test$ ls a.out main.c main.i main.o main.s octopus@octopus:~/test$ ./a.out Hello World ! num = 5 查看预处理细节 : 使用 gcc -E mian.c 命令, 会输出编译细节, 打印出上千行, 这里仅仅贴出部分;
octopus@octopus:~/test$ gcc -E main.c # 1 "main.c" # 1 "<built-in>" # 1 "<命令行>" # 1 "main.c" ... # 1 "/usr/include/stdio.h" 1 3 4 # 28 "/usr/include/stdio.h" 3 4 # 1 "/usr/include/features.h" 1 3 4 # 324 "/usr/include/features.h" 3 4 ... # 9 "main.c" 2 int main(int argc, char **argv) { printf("Hello World ! \n"); return 0; } 在 gcc 命令行中进行宏定义 : 使用 gcc -DNUM=5 main.c 命令, 在程序中就能够使用 NUM 宏定义了, "-DNUM" 相当于在程序中定义了 "#define NUM 5"; -- main.c 内容 :
/************************************************************************* > File Name: main.c > Author: octopus > Mail: octopus_work.163.com > Created Time: 2014年04月30日 星期三 17时31分08秒 ************************************************************************/ #include<stdio.h> int main(int argc, char **argv) { printf("Hello World ! num = %d \n", NUM); return 0; }-- 编译过程 :
octopus@octopus:~/test$ gcc -DNUM=5 main.c octopus@octopus:~/test$ ./a.out Hello World ! num = 5 2. 编译编译流程 : 编译器在编译阶段依次运行 词法分析, 语法分析, 代码优化, 存储分配, 代码生成 五个步骤; -- 多次扫描方案 : 编译器每次扫描代码仅仅完毕一项工作, 如 第一次扫描 仅仅进行词法分析, 第二次扫描进行 语法分析, 扫描多次完毕上面的五个步骤; 生成中间的汇编中间文件 : 使用 gcc -S main.c 编译上面的 main.c 源程序, 能够得到 mian.s 汇编语言文件, 这是产生的中间汇编程序; -- 编译过程 及 结果 :
octopus@octopus:~/test$ gcc -S main.c octopus@octopus:~/test$ cat main.s .file "main.c" .section .rodata .LC0: .string "Hello World ! num = %d \n" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 andl $-16, %esp subl $16, %esp movl $.LC0, %eax movl $5, 4(%esp) movl %eax, (%esp) call printf movl $0, %eax leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3" .section .note.GNU-stack,"",@progbits octopus@octopus:~/test$ 3. 汇编汇编过程 : 汇编 就是将 汇编语言代码 翻译成 机器码, 也就是 ".o" 后缀的对象文件, 该过程 使用 汇编器 as 实现; 获取中间文件 : "-c" 选项能够保留 汇编过程中的 ".o" 后缀的中间文件, 使用 gcc -c main.c 命令, 能够获得 main.o 对象文件;
octopus@octopus:~/test$ ls main.c octopus@octopus:~/test$ gcc -c main.c octopus@octopus:~/test$ ls main.c main.o 4. 连接链接过程 : 使用 ld 连接器, 将 汇编 过程中生成的 ".o" 对象文件, 与其他 对象文件 和 库文件连接起来, 生成可运行的二进制文件; 连接演示样例 : 使用 gcc main.o 将汇编过程生成的对象文件 main.o , 生成可运行文件 a.out ;
octopus@octopus:~/test$ gcc main.o octopus@octopus:~/test$ ./a.out Hello World ! num = 5 二. 编译C程序1. 编译单个C程序C语言程序演示样例 : 简单的Hello World;
/************************************************************************* > File Name: main.c > Author: octopus > Mail: octopus_work.163.com > Created Time: 2014年04月19日 星期六 16时22分26秒 ************************************************************************/ #include<stdio.h> int main(int argc, char **argv) { printf("Hello World! \n"); return 0; } 简单编译 : 使用 gcc main.c 命令, 会生成 a.out 可运行文件, 使用 ./a.out 能够运行编译好的C程序;
octopus@octopus:~/gcc$ gcc main.c octopus@octopus:~/gcc$ ./a.out Hello World! 指定输出文件编译 : 假设不想使用 a.out 作为输出文件, 能够使用 -o 參数指定输出文件, 假设该文件存在就会覆盖; -- 命令 : gcc main.c -o main;
octopus@octopus:~/gcc$ gcc main.c -o main octopus@octopus:~/gcc$ ./main Hello World! 显示警告选项 : -Wall 选项, 能够在编译的时候, 将警告信息输出到终端中; -- 编译输出警告信息 : gcc -Wall main.c; 人为制造警告 : 在 printf 输出的时候, 使用 %s 作为一个 int 数据的占位符;
/************************************************************************* > File Name: main.c > Author: octopus > Mail: octopus_work.163.com > Created Time: 2014年04月19日 星期六 16时22分26秒 ************************************************************************/ #include<stdio.h> int main(int argc, char **argv) { printf("Hello World! num = %s\n", 4); return 0; }-- 运行编译 : gcc -Wall main.c, 编译的时候报出警告, 可是编译通过, 可是运行的时候就出错了;
octopus@octopus:~/gcc$ gcc main.c main.c: 在函数‘main’中: main.c:12:2: 警告: 格式 ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘int’ [-Wformat] octopus@octopus:~/gcc$ ./a.out 段错误 (核心已转储) 2. 编译多个文件由三个文件组成的程序 : kill.h, kill.c, main.c, 当中 main.c 是主函数入口, 调用 kill.c 定义的方法; -- kill.h 内容 : 声明 kill 方法, 引用了该头文件, 就可以使用 kill 方法;
/************************************************************************* > File Name: kill.h > Author: octopus > Mail: octopus_work.163.com > Created Time: 2014年04月19日 星期六 20时51分59秒 ************************************************************************/ #ifndef KILL int kill(char *); #endif-- kill.c 内容 : 主要实现 kill.h 中声明的 kill 方法;
/************************************************************************* > File Name: kill.c > Author: octopus > Mail: octopus_work.163.com > Created Time: 2014年04月19日 星期六 20时53分53秒 ************************************************************************/ #include<stdio.h> int kill(char *ch) { printf("%s \n", ch); return 0; }-- mian.c内容 : 引用 kill.h 库;
/************************************************************************* > File Name: main.c > Author: octopus > Mail: octopus_work.163.com > Created Time: 2014年04月19日 星期六 16时22分26秒 ************************************************************************/ #include<stdio.h> #include"kill.h" int main(int argc, char **argv) { printf("Hello World! \n"); kill("fuck"); return 0; } 引用头文件库符号差别 : #include"kill.h" #include<kill.h> ; -- #include "kill.h" : 先在当前文件夹搜索 kill.h 头文件, 在到系统中搜索该头文件; -- #include <kill.h> : 直接去系统库中寻找头文件, 不会搜索当前文件夹; 编译文件 : 使用 gcc -Wall main.c kill.c -o kill 进行编译;
octopus@octopus:~/gcc$ gcc -Wall main.c kill.c -o kill octopus@octopus:~/gcc$ ./kill Hello World! fuck 3. 独立编译文件开发需求 : 当一个项目比較大的时候, 整个项目编译时间会非常长, 假设改变一个函数就须要又一次编译整个项目, 就会非常浪费时间; -- 解决方式 : 程序被存储在多个源文件里, 每一个源文件都单独进行编译; 单独编译多个源文件步骤 : 首先生成 对象文件, 再将对象文件链接生成可运行文件; -- 编译对象文件 : 将源程序编译成不可运行的文件, 生成 .o 后缀的对象文件; -- 链接程序 : gcc 中有一个链接器将全部的对象文件链接到一起, 生成一个可运行文件; 解析对象文件 : 文件里存放的是机器码, 机器码中对其他文件里的 函数 或者 变量引用的地址没有解析, 当链接程序的时候才将这些地址写入; 生成对象文件 : -c 參数用于生成 对象文件; -- 生成kill.o对象文件 : gcc -Wall -c kill.c , 会生成 kill.o 文件, 该对象文件里引用 kill 方法, 该方法相应的地址没有被解析;
octopus@octopus:~/gcc$ gcc -Wall -c kill.c octopus@octopus:~/gcc$ ls kill.c kill.h kill.o main.c-- 生成 main.o对象文件 : gcc -Wall -c mian.c, 生成 main.o 文件;
octopus@octopus:~/gcc$ gcc -Wall -c main.c octopus@octopus:~/gcc$ ls kill.c kill.h kill.o main.c main.o 链接对象文件 : gcc main.o kill.o -o main 命令, 链接 main.o 和 kill.o 两个对象文件; -- 不许要-Wall參数 : 链接程序仅仅有两种结果, 成功 或者 失败, 不许要警告信息了; -- 链接器 : gcc中ld链接器 用来链接对象文件;
octopus@octopus:~/gcc$ gcc main.o kill.o -o main octopus@octopus:~/gcc$ ./main Hello World! fuck 对象文件的链接次序 : 大部分编译器都能够任意排列顺序, 可是有的编译器须要注意链接次序; -- 编译器和连接器次序 : 编译器和链接器搜索外部函数 是 从左到右进行查找; -- 文件次序 : 调用函数的 对象文件, 该文件应该先于 定义函数的 对象文件, 这里 main.o 应该在 kill.o 之前; -- 错误排查 : 假设在编译程序的时候, 列出了全部的文件, 可是还出现了 没有定义 错误, 就须要注意 文件排列的问题; 改动文件流程 : 当改动了一个文件之后, 仅仅须要 又一次编译这个文件就可以。 之后将这个新编译的对象文件 与 原来的对象文件进行链接, 就可以生成新的可运行文件; -- 又一次编译 : 当改动了一个文件之后, 仅仅须要将这个文件又一次编译成 对象文件就可以; -- 又一次链接 : 将新编译的对象文件, 与之前已经编译好的 其他源文件的对象文件进行链接就可以; , |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论