在使用C/C++开发程序时,编译链接是非常重要的步骤。编译器会将源代码转换为机器码,链接器会将编译器生成的目标文件和库文件组合成可执行文件。
先使用gcc编译生成可执行文件helloworld,再运行helloworld,相关命令如下:
gcc/g++会将源代码文件编译成中间代码文件(.o文件),然后将这些中间代码文件链接成可执行文件。在编译和链接过程中,我们可以指定不同的选项来控制编译和链接的行为。以下是简单的编译和链接过程的十一图(以helloworld.c为例)。
1.1预处理过程
预处理阶段由cpp来完成,这里的cpp不是"c plus plus"的意思,而是"the C Preprocessor"的意思。cpp会在编译之前对源文件进行处理。它会对自定义和预定义的宏进行展开,把包含的头文件内容插入到当前源文件,并根据预处理指令包含不同的代码,或者设置设置传递给编译器的参数。
我们可以使用gcc的-E和-v选项来查看整个预处理过程。其中,-E选项表示只对源文件做预处理,-v选项表示显示详细的预处理过程,-o选项表示将预处理后的结果输出到helloworld.i文件中。
从下面输出可以看出,系统头文件搜索的路径。
1.2编译阶段
编译阶段由编译器ccl把预处理后的文件内容转换成汇编程序。我们可以使用gcc的-S选项来实现编译的过程。-S选项便是只做编译处理,生成的汇编代码如下。
biocare@biocare:~/桌面/test$ cat helloworld.s
.file “helloworld.c”
.text
.section .rodata
.LC0:
.string “Yang Jian ate dog meat pot”
.LC1:
.string “%s hello world ”
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
leaq .LC0(%rip), %rax
movq %rax, -8(%rbp)
movq -8(%rbp), %rax
movq %rax, %rsi
leaq .LC1(%rip), %rdi
movl $0, %eax
call printf@PLT
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident “GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0”
.section .note.GNU-stack,“”,@progbits
1.3 汇编阶段
紧接着,汇编器as会把helloworld.s的内容翻译成机器指令,这些机器指令按照ELF(Executable and Linking Format)被打包成重定位的二进制目标文件。使用-c选项来实现目标文件的生成,file命令用于查看目标文件的信息。
在file命令的输出中,“ELF 64-bit LSB relocatable”表示64位的、格式为ELF的可重定位文件,“not stripped”表示文件中的符号和调试信息未被删除。
ELF是一种常用的二进制文件格式,它支持动态链接和装载,可以用于生成可执行文件、共享库和可重定位文件等。64位的ELF是一种针对64位处理器的二进制文件格式。(ELF相关介绍移步到ELF文章中进行学习,ELF文件概述)
文件中的符号和调试信息可以用于调试和分析程序。如果需要生成发布版本的程序,可以使用strip命令删除这些信息,以减小程序的大小。
1.4 链接阶段
因为helloworld.c中引用了标准C库中的printf函数,所以在最后的链接阶段,ld链接器会把printf函数符号位于libc.so.6库中的重定位和符号表信息复制到最终的可执行文件helloworld 中。gcc链接时库的搜索路径在使用-v选项时会显示出来,libc.so.6库就是在这些路径中进行搜索的。
gcc -v -o helloworld helloworld.o
在实际的C/C++工程项目中,一个程序通常由多个代码文件构成,应该如何编译工程项目的程序呢?假设我们的项目中有3个源文件和两个头文件,它们分别是print1.c、print2.c、main.c、print1.h、print2.h。
print1.h
print1.c
print2.h
print2.c
main.c
我们可以手动完成整个编译和链接的过程。
2.1 makefile
在项目开发中,我们经常需要修改源代码文件,如果每次修改都需要手动进行编译和链接,显然非常耗时和低效。那么,有没有什么快捷的方式,使得我们能够根据代码文件的变更来完成程序的重新编译和链接呢?
在Linux系统中,make命令是一个自动化编译工具,可以自动完成程序的编译和链接。我们只需要编写一个名为"makefile"的文件,在这个文件中包含整个程序的编译、链接规则,然后执行make命令,make命令就会在当前目录下搜索makefile文件并解析执行其中的编译、链接命令,从而完成整个程序的编译和链接。
makefile文件在本质上就是一个编译、链接脚本,其中包含了所有的编译、链接规则,并且定义了程序中各个代码文件之间的依赖关系和编译、链接选项。make命令就是makefile这个脚本的解析器和执行器,make和 makefile的关系,同shell和 shell脚本的关系类似。make命令并不是每次都简单地重新执行makefile文件中的所有编译、链接命令,如果是这样的话,就和 shell脚本没有区别了。相反,make命令会根据源代码文件的变更情况,只重新编译必要的目标文件并重新链接生成可执行文件,从而提高了编译、链接的效率。如果修改了某个.c文件,那么对应的目标文件就会被重新编译生成。如果源代码文件没做任何变更,那么make命令不会执行makefile文件中的任何编译、链接命令,而只在终端输出当前的可执行文件是最新文件的提示。
2.2 makefile的基本语法
自行了解
2.3 make命令的工作方式
• make命令会在当前目录下查找名为“makefile”的文件,也可以使用-f选项指定特定的文件为makefile文件。
• 如果没有找到makefile文件,make命令就会报错,提示找不到makefile文件。如果找到了makefile文件,默认情况下,make命令会把makefile文件中出现的第一个target作为最终生成的目标。当然,我们也可以在执行make命令时,指定生成特定的target。
• make命令会根据makefile文件中描述的依赖关系和最终所要生成的target来执行相应的命令,最后生成目标文件。具体来说,如果target是最新生成的,那么make命令不会执行makefile文件中的任何命令;如果target不存在或者target不是最新的,那么make命令会执行makefile文件中生成target所关联的命令,并根据需要递归地执行生成其他依赖文件的命令;如果 target关联的某些源代码文件被修改,或者target的某些依赖文件缺失,那么make命令会生成最新的依赖文件,并执行makefile文件中生成target所关联的命令。
• make命令在执行编译和链接的过程中,不会理会关联命令的错误,而只处理依赖关系。如果依赖的文件无法生成,make命令会直接报错并退出;否则,make命令会根据makefile文件中描述的依赖关系,递归地执行关联的命令,直至生成最终的target。
三、常用的编译和链接选项
3.1 动态链接库选项
• -l 选项
-l选项指定要链接哪个动态库,假设我们在程序中使用了pthread系列的多线程函数,则在链接时需要指定-lpthread这个选项。
• -L 选项
-L选项指明了动态库所在的目录。-L选项几乎被用于指明第三方动态库或自定义动态库所在的目录,因为第三方的或自定义的动态库都有自己独立的存储目录。
• -WI,-rpath 选项
在“-Wl,-rpath”选项中,-WI表示给链接器传递参数,传递的参数为逗号后面的-rpath。-rpath为实际传递给链接器的参数,它用于指定运行时动态链接库优先的搜索路径,这条搜索路径的信息被保存在可执行文件中。
• -Ⅰ头文件选项
-Ⅰ头文件选项在工程项目中最为常见。当编译到引用了动态库符号的源代码时,就需要指定-Ⅰ头文件选项,用于指明引用的动态库符号声明的头文件所在的目录。
3.2 宏选项
宏选项是编译的开关,通过这些开关,我们可以选择性地实现程序不同的功能。
• -D 宏选项
-D宏选项在编译阶段使用,用于影响预处理的结果。它能够在编译阶段就动态选择程序实现的功能。
下面我们来看看在使用和不使用-D选项的情况下,程序的运行结果有何不同。
• -DTEST=VALUE
从上面的运行结果中可以看出,当添加-DTEST 选项时,宏的定义会被传递到源代码文件defineTest.c中。当然,我们也可以像在程序中给宏指定值一样,在编译参数中给宏指定值。例如,如果想要指定TEST宏的值为2,那么可以使用-DTEST=2选项。
• -DNDEBUG 宏选项
-DNDEBUG选项用于关闭assert断言函数。当我们使用-DNDEBUG选项进行编译时,代码中的assert 断言函数都会失效,不会进行任何检查。
assert断言函数通常用于在程序中进行调试和错误处理。它会检查一个条件是否成立,如果这个条件不成立,则输出一条错误信息,并终止程序的执行。在开发阶段,我们通常会开启 assert 断言函数,以便及早发现程序中的问题。但在发布程序时,我们通常会关闭assert 断言函数,以提高程序的性能。
3.3 调试与告警选项
在程序开发过程中,我们难免需要对程序进行调试,并且需要编译器帮助我们检查程序中的错误。此时,调试与告警选项就派上用场了。
• -g 选项
-g 选项用于在编译时生成调试信息。这些调试信息可以被调试工具(如gdb)用来进行程序调试。在使用gdb 进行调试时,我们可以查看程序的源代码、变量的值、函数的调用栈等信息,从而更方便地进行调试。
需要注意的是,使用-g 选项会增大程序的大小,因为编译器会将调试信息嵌入可执行文件。因此,在发布程序时,我们通常会关闭-g 选项,以减小程序的大小。
• -Wall 选项
-Wall选项是用于生成所有告警信息的编译选项。它可以帮助我们发现可能隐藏的问题,从而提高程序的质量。
具体来说,-Wall选项会生成所有的告警信息,包括一些常见的编程错误,如未声明的变量、未使用的变量、类型不匹配等。这些告警信息可以帮助我们发现代码中的潜在问题,从而及早地进行修复,避免在后期出现更严重的问题。
• -Wextra 选项
只打开-Wall选项是不够严格的,-Wextra选项是对-Wall选项的补充。
-Wall选项虽然能够生成大量的告警信息,但并不包括所有的告警类型。这时,我们可以使用-Wextra选项来对-Wal1选项进行补充。
具体来说,-Wextra选项包括一些没有被-Wall选项包含在内的告警类型,如未使用的函数、未使用的参数、类型转换等。使用-Wextra选项可以帮助我们发现更多潜在的问题,从而提高程序的质量。
声明:禁止转载等其他用途,仅供学习,以上内容如有雷同,请私信本作者,进行修改。
到此这篇devc++反编译(c+ 反编译)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/cjjbc/82181.html