怎样保证网站的安全性网站建设没业务
news/
2025/10/3 19:12:08/
文章来源:
怎样保证网站的安全性,网站建设没业务,免费模板下载网站推荐,网页在线代理再谈编译链接
C函数重载与编译链接-CSDN博客 之前我已经写过文章简单介绍了编译链接要做的一些操作。现在为了能更好的理解我们平时的开发环境#xff0c;我会在Linux系统上完整地走一遍流程。
环境描述 我们使用普通用户在Linux上进行操作#xff0c;先写一段测试代码。
…再谈编译链接
C函数重载与编译链接-CSDN博客 之前我已经写过文章简单介绍了编译链接要做的一些操作。现在为了能更好的理解我们平时的开发环境我会在Linux系统上完整地走一遍流程。
环境描述 我们使用普通用户在Linux上进行操作先写一段测试代码。
[ssddffaacode code]$ vim test.c#include stdio.hint main()
{printf(Hello World!\n);return 0;
} 接下来我们使用 gcc 编译器编译一下我们的文件。
[ssddffaacode code]$ gcc test.c
[ssddffaacode code]$ ls
a.out test.c
[ssddffaacode code]$ ll
total 16
-rwxrwxr-x. 1 ssddffaa ssddffaa 8360 Mar 28 09:32 a.out
-rw-rw-r--. 1 ssddffaa ssddffaa 75 Mar 28 09:31 test.c
[ssddffaacode code]$ 可以看到默认生成了一个带可执行权限的文件 a.out 接着我们运行它。
[ssddffaacode code]$ ./a.out
Hello World! 要是不想要默认生成的可执行文件我们也可以使用 gcc 的 -o 参数改变 gcc 的输出。
[ssddffaacode code]$ gcc test.c -o test.txt
[ssddffaacode code]$ ll
total 28
-rwxrwxr-x. 1 ssddffaa ssddffaa 8360 Mar 28 09:32 a.out
-rw-rw-r--. 1 ssddffaa ssddffaa 75 Mar 28 09:31 test.c
-rwxrwxr-x. 1 ssddffaa ssddffaa 8360 Mar 28 09:36 test.txt[ssddffaacode code]$ ./test.txt
Hello World! 可以看到我们重新生成了一个可执行文件 test.txt。可能你会疑惑这个文件的后缀名是 txt 怎么还能执行他不应该是一个记事本嘛 在Linux系统中文件是否能被运行有两个条件一是文件具有可执行权限二是文件本身具有可执行的功能。以上两个条件我们的 text.txt 都满足了当然可以正常执行。相对的如果一个文件只有可执行权限但是文件本身本没有可执行能力那也是执行不了的。
编译链接
预处理 下面我们来看一看 test.c 文件经过预处理后会发生什么。通过 gcc 加 -E 参数可以使文件编译到预处理阶段完成后就停止并把结果打印到屏幕上使用 -o 参数可以重定向到指定文件中。
[ssddffaacode code]$ gcc -E test.c -o test.i
[ssddffaacode code]$ ll
total 24
-rw-rw-r--. 1 ssddffaa ssddffaa 75 Mar 28 09:31 test.c
-rw-rw-r--. 1 ssddffaa ssddffaa 16872 Mar 28 09:45 test.i[ssddffaacode code]$ vim test.i
825 extern char *ctermid (char *__s) __attribute__ ((__nothrow__ , __leaf__));
826 # 913 /usr/include/stdio.h 3 4
827 extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
828
829
830
831 extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
832
833
834 extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
835 # 943 /usr/include/stdio.h 3 4
836
837 # 2 test.c 2
838
839 int main()
840 {
841
842 printf(Hello World!\n);
843 return 0;
844 } 当我们进入 test.i 文件中进行查看时会发现原本我们只写了几行的代码在 test.i 文件中增加到了800多行。我们多出来的这些代码是什么呢我想你应该已经猜到了这是预处理阶段要做的一件事 头文件展开。没错多出来的这几百行代码就是 #include stdio.h 的效果那么这些代码是凭空出现的嘛显然不是。头文件展开其实就是去把Linux系统中的头文件内容拷贝了一份然后放进了我们的代码。 我们可以在系统中找一下存放头文件的位置。
[ssddffaacode code]$ sudo find / -name stdio.h
[sudo] password for ssddffaa:
/usr/include/c/4.8.2/tr1/stdio.h
/usr/include/bits/stdio.h
/usr/include/stdio.h 出现了3个路径不过只有最下面那个才是我们使用的头文件这一点可以通过查看 gcc 的默认include路径了解。
[ssddffaacode include]$ gcc -print-prog-namecc1plus -v
ignoring nonexistent directory /usr/lib/gcc/x86_64-redhat-linux/4.8.5/include-fixed
ignoring nonexistent directory /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../x86_64-redhat-linux/include
#include ... search starts here:
#include ... search starts here:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../include/c/4.8.5/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../include/c/4.8.5/x86_64-redhat-linux/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../include/c/4.8.5/backward/usr/lib/gcc/x86_64-redhat-linux/4.8.5/include/usr/local/include/usr/include
End of search list. 可以看到我们包含的头文件确确实实是存在在我们的计算机上的。平时我们使用的 printf 等函数并没有我们自己实现但是我们就在使用了这是因为我们包含了头文件然后在预处理时编译器会把我们包含的头文件内容拷贝到我们写的代码里然后在之后的编译阶段编译器才能识别到 printf 等库函数接着进行下一步操作。 预处理阶段不仅仅只是展开头文件还有以下内容。
#1 将#define删除,并且展开所有的宏定义
#2 处理条件编译,#if #ifdef #elif #endif等
#3 头文件展开
#4 删除注释
#5 添加行号和文件标识,以便编译时产生调试试用的行号以及编译错误警告号
#6 保留所有的#pragma编译器指令,因为编译器需要使用它们
编译 预处理完成后会进入编译阶段在这个阶段我们之前处理过的 test.i 文件会进一步进行处理。编译阶段在整个编译链接中是最复杂的一个。 编译过程可以分为6步:
#1 词法分析:扫描器(Scanner)将源代的字符序列分割成一系列的记号(Token)
#2 语法分析:语法分析器将记号(Token)产生语法树(Syntax Tree)
#3 语义分析:静态语义(编译器可以确定的语义),动态语义(只能在运行期才能确定的语义)
#4 源代码优化:源代码优化器,将整个语法树转换为中间代码(中间代码是与目标机器和运行环境无关的)中间使得编译器被分为前端和后端,编译器前端负责产生机器无关的中间代码编译器后端将中间代码转化为目标机器代码
#5 目标代码生成:代码生成器(Code Generator)
#6 目标代码优化:目标代码优化器(Target Code Optimizer) 我们代码语法的检查代码的优化等都是在编译阶段完成的。在编译阶段还会生成符号表它用于链接阶段。 使用 gcc 加 -S 参数可以让文件编译执行到编译阶段完成后终止。
[ssddffaacode code]$ gcc -S test.c -o test.s
[ssddffaacode code]$ ll
total 28
-rw-rw-r--. 1 ssddffaa ssddffaa 75 Mar 28 09:31 test.c
-rw-rw-r--. 1 ssddffaa ssddffaa 16872 Mar 28 09:45 test.i
-rw-rw-r--. 1 ssddffaa ssddffaa 448 Mar 28 20:23 test.s[ssddffaacode code]$ vim test.s1 .file test.c2 .section .rodata3 .LC0:4 .string Hello World!5 .text6 .globl main7 .type main, function8 main:9 .LFB0:10 .cfi_startproc11 pushq %rbp12 .cfi_def_cfa_offset 1613 .cfi_offset 6, -1614 movq %rsp, %rbp15 .cfi_def_cfa_register 616 movl $.LC0, %edi17 call puts18 movl $0, %eax19 popq %rbp20 .cfi_def_cfa 7, 821 ret22 .cfi_endproc23 .LFE0:24 .size main, .-main25 .ident GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)26 .section .note.GNU-stack,,progbits 可以看到 test.s 文件里是汇编代码。
汇编阶段 在汇编阶段汇编器将 test.s 文件翻译成机器语言指令把这些指令打包成一种叫做可重定位二进制目标程序的格式并将结果保存在目标文件 test.o 中test.o 是一个二进制文件。 使用 gcc 加 -c 参数可以让文件编译执行到汇编阶段完成后终止。
[ssddffaacode code]$ gcc -c test.c -o test.o
[ssddffaacode code]$ ll
total 32
-rw-rw-r--. 1 ssddffaa ssddffaa 75 Mar 28 09:31 test.c
-rw-rw-r--. 1 ssddffaa ssddffaa 16872 Mar 28 09:45 test.i
-rw-rw-r--. 1 ssddffaa ssddffaa 1496 Mar 28 20:40 test.o
-rw-rw-r--. 1 ssddffaa ssddffaa 448 Mar 28 20:33 test.s[ssddffaacode code]$ vim test.o 使用 vim 文本编辑器进入 test.o 查看会全是乱码这是因为 test.o 中是二进制数据而 vim 只能识别文本所以我们换一个工具进行查看。
[ssddffaacode code]$ hexdump test.o 可以看到文件由二进制转为16进制的内容。我们的计算机能识别二进制而我们的 test.o 文件就是一个二进制文件那我们可以不可以运行它呢我们来试一下。
[ssddffaacode code]$ ll
total 32
-rw-rw-r--. 1 ssddffaa ssddffaa 75 Mar 28 09:31 test.c
-rw-rw-r--. 1 ssddffaa ssddffaa 16872 Mar 28 09:45 test.i
-rw-rw-r--. 1 ssddffaa ssddffaa 1496 Mar 28 20:40 test.o
-rw-rw-r--. 1 ssddffaa ssddffaa 448 Mar 28 20:33 test.s
[ssddffaacode code]$ chmod x test.o
[ssddffaacode code]$ ll
total 32
-rw-rw-r--. 1 ssddffaa ssddffaa 75 Mar 28 09:31 test.c
-rw-rw-r--. 1 ssddffaa ssddffaa 16872 Mar 28 09:45 test.i
-rwxrwxr-x. 1 ssddffaa ssddffaa 1496 Mar 28 20:40 test.o
-rw-rw-r--. 1 ssddffaa ssddffaa 448 Mar 28 20:33 test.s
[ssddffaacode code]$ ./test.o
-bash: ./test.o: cannot execute binary file 可以看到报了错误 无法执行的二进制文件。汇编生成的二进制文件还不具有可执行的能力这是因为缺少了库文件。我们在预处理阶段包含的头文件只是声明声明和定义是分离的定义是存储在库文件中的同时库中还包含了许多方法。
链接 汇编过后生成的目标文件*.o并不是最终的可执行二进制文件而是一种中间文件或临时文件目标文件任然需要经过链接才能变成可执行文件。 在链接阶段链接器会把我们写的所有目标文件*.o与系统组件标准库动态链接库等结合起来生成一个可执行程序。链接程序运行需要的一大堆目标文件以及所依赖的其它库文件最后生成可执行文件。
[ssddffaacode code]$ gcc test.c -o test
[ssddffaacode code]$ ll
total 44
-rwxrwxr-x. 1 ssddffaa ssddffaa 8360 Mar 28 21:47 test
-rw-rw-r--. 1 ssddffaa ssddffaa 75 Mar 28 09:31 test.c
-rw-rw-r--. 1 ssddffaa ssddffaa 16872 Mar 28 09:45 test.i
-rw-rw-r--. 1 ssddffaa ssddffaa 1496 Mar 28 20:40 test.o
-rw-rw-r--. 1 ssddffaa ssddffaa 448 Mar 28 20:33 test.s
[ssddffaacode code]$ ./test
Hello World! Linux下的C语言的标准库文件在 /usr/lib64/ 下本质就是一个文件。
[ssddffaacode code]$ ll /usr/lib64/libc.so*
-rw-r--r--. 1 root root 253 May 18 2022 /usr/lib64/libc.so
lrwxrwxrwx. 1 root root 12 Mar 27 08:53 /usr/lib64/libc.so.6 - libc-2.17.so Linux和Windows下的库文件。
# Linux: .so(动态库) .a(静态库)
# Windows .dll(动态库) .lib(静态库)库的命名规则
# libname.so.XXX
# 其中lib是库前缀名,name则是库的名称,so是库后缀,XXX是版本号 编译型语言安装开发包必定是下载安装对应的头文件和库文件。 库其实就是把源文件.c经过一定的翻译然后打包形成一个文件。不用给你提供太多的源文件也可以达到隐藏源文件的目的。 头文件提供声明库文件提供实现 你的代码 你的软件。
动态链接与静态链接 库有动态库和静态库链接也有动态链接和静态链接。
动态链接 动态库是一个共享的库文件当程序开始执行时编译器便会告诉程序动态库在哪个位置有哪些方法。当程序执行到需要库里的文件时便会跑到库里去执行执行完又继续执行下一步操作。像这样编译器告诉程序动态库的位置然后程序需要时跑去执行的方式叫动态链接。 动态库不能缺失一旦对应的动态库缺失影响的不止一个程序可能导致很多程序都无法正常运行。
静态链接 静态链接就相当于直接把静态库中要用到的内容直接和我们的目标文件链接在一起生成可执行程序。完成后就算静态库没了程序也能正常运行。
比较 动态链接和静态链接各有各的优势当一个库文件有许多程序都需要使用时用动态链接可以让程序不用去每个都拷贝一份库文件。而且更新库文件时方便你只需要改库文件就可以完成库的更新。 静态链接可以让程序不依赖之前的静态库文件减少重定向链接到动态库的开销提高了程序效率。但是这个效率只提高了5%左右。更新库时麻烦需要重新修改静态库文件然后再编译链接一次生成新的可执行文件。
验证 使用 ldd 可以查看程序的动态链接情况
[ssddffaacode code]$ ldd testlinux-vdso.so.1 (0x00007ffc36b9c000)libc.so.6 /lib64/libc.so.6 (0x00007f4ff73f3000)/lib64/ld-linux-x86-64.so.2 (0x00007f4ff77c1000) 可以看到在Linux下 gcc 默认使用的是动态链接。 在编译时加上 -static 参数可以让 gcc 使用静态链接。
[ssddffaacode code]$ gcc test.c -o test-static -static
/usr/bin/ld: cannot find -lc
collect2: error: ld returned 1 exit status 报错了这个报错的原因呢是因为我们的Linux系统上默认是没有静态库的需要我们手动去安装一下。
[ssddffaacode code]$ sudo yum install -y glibc-static
[ssddffaacode code]$ sudo yum install -y libstdc-static 安装完成后我们再去试一下。
[ssddffaacode code]$ gcc test.c -o test-static -static
[ssddffaacode code]$ ll -h
total 888K
-rwxrwxr-x. 1 ssddffaa ssddffaa 8.2K Mar 28 21:47 test
-rw-rw-r--. 1 ssddffaa ssddffaa 75 Mar 28 09:31 test.c
-rw-rw-r--. 1 ssddffaa ssddffaa 17K Mar 28 09:45 test.i
-rw-rw-r--. 1 ssddffaa ssddffaa 1.5K Mar 28 20:40 test.o
-rw-rw-r--. 1 ssddffaa ssddffaa 448 Mar 28 20:33 test.s
-rwxrwxr-x. 1 ssddffaa ssddffaa 842K Mar 29 01:41 test-static
[ssddffaacode code]$ ldd test-static not a dynamic executable 可以看到成功生成了一个可执行程序 test-static并且它没有使用任何动态库。但是 test-static 和 test 的大小起来大了80K左右。
#1 如果我们没有静态库,但是我们就要-static,是不行的
#2 如果我们没有动态库,只有静态库,而且gcc能找到不使用-staticgcc还是会链接成功,并且使用静态库这是因为gcc默认使用动态链接,使用-static只是增加了静态链接的优先级
#3 不一定是纯的全部动态链接或静态链接,是混合的 查看可执行程序使用的库情况。
[ssddffaacode code]$ file test
test: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]9fc19efe5a60448b3a2d19e1e150f7b77d65993a, not stripped
[ssddffaacode code]$ file test-static
test-static: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]7cd92dd71613a6bad6eea55cc7d520b28e98e9d7, not stripped
Debug Release Debug 模式相比 Release 模式增加了调试信息可以方便开发人员对代码进行调试。gcc 默认使用的是 Release 模式要使用 Debug 模式需要加上参数 -g。
[ssddffaacode code]$ gcc test.c -o test_debug -g
[ssddffaacode code]$ gcc test.c -o test-static_debug -static -g
[ssddffaacode code]$ ll
total 1716
-rwxrwxr-x. 1 ssddffaa ssddffaa 8360 Mar 28 21:47 test
-rw-rw-r--. 1 ssddffaa ssddffaa 75 Mar 28 09:31 test.c
-rwxrwxr-x. 1 ssddffaa ssddffaa 9360 Mar 29 02:09 test_debug
-rwxrwxr-x. 1 ssddffaa ssddffaa 861288 Mar 29 01:41 test-static
-rwxrwxr-x. 1 ssddffaa ssddffaa 862296 Mar 29 02:10 test-static_debug 可以看到 Debug 模式生成的可执行程序都比 Release 模式下生成的可执行程序大这是因为 Debug 模式增加了调试信息。
[ssddffaacode code]$ readelf -S test_debug | grep debug[27] .debug_aranges PROGBITS 0000000000000000 00001061[28] .debug_info PROGBITS 0000000000000000 00001091[29] .debug_abbrev PROGBITS 0000000000000000 00001122[30] .debug_line PROGBITS 0000000000000000 00001164[31] .debug_str PROGBITS 0000000000000000 0000119f
make/Makefile 一个工程中的源文件不计其数按类型、功能、模块分别放在若干个目录中Makefile 定义了一系列的规则来指定哪些文件需要先编译哪些文件需要后编译哪些文件需要重新编译甚至于进行更复杂的功能操作。 make是一条指令Makefile是一个当前目录下的文件。
[ssddffaacode code]$ touch Makefile
[ssddffaacode code]$ vim Makefile1 test.exe:test.c #编写 Makefile 第一行是依赖关系2 gcc -o test.exe test.c #第二行是以TAB开始然后输入依赖方法。3 .PHONY:clean #加上PHONY参数修饰clean4 clean: #清理5 rm -f test.exe #清理方法通过 make 指令来执行 Makefile 文件内容。
[ssddffaacode code]$ make
gcc -o test.exe test.c
[ssddffaacode code]$ ll
total 20
-rw-rw-r--. 1 ssddffaa ssddffaa 40 Mar 29 02:41 Makefile
-rw-rw-r--. 1 ssddffaa ssddffaa 75 Mar 28 09:31 test.c
-rwxrwxr-x. 1 ssddffaa ssddffaa 8360 Mar 29 02:41 test.exe
[ssddffaacode code]$ ./test.exe
Hello World!
[ssddffaacode code]$ make clean
rm -f test.exe
[ssddffaacode code]$ ll
total 8
-rw-rw-r--. 1 ssddffaa ssddffaa 76 Mar 29 02:50 Makefile
-rw-rw-r--. 1 ssddffaa ssddffaa 75 Mar 28 09:31 test.c 可以看出 make/Makefile 能把我们从冗余的 gcc 命令中解脱出来只需 make 和 make clean 就可以构建和删除项目。
参考文章
程序详细编译过程预处理、编译、汇编、链接 - 知乎 (zhihu.com)
详解C/C代码的预处理、编译、汇编、链接全过程 - 知乎 (zhihu.com)
深入浅出静态链接和动态链接-CSDN博客
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/926225.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!