c 工程在 main.c 里面引入其他模块,为什么要引入这个模块的.h 文件呢

2018-07-27 09:41:55 +08:00
 thomaswang

最好能讲解的详细点, 多谢

编译,汇编,链接,可重定位目标文件,符号,符号表

3131 次点击
所在节点    问与答
23 条回复
ian19znj
2018-07-27 09:53:26 +08:00
因为需要知道函数,变量以及宏的声明,否则无法调用。
msg7086
2018-07-27 10:22:48 +08:00
不一定要引入这个模块的.h 文件。

只要在调用前申明你所用到的文件外的函数、变量还有宏之类的就行了。
.h 文件只是简化这个过程。
LuckyKoala
2018-07-27 10:40:12 +08:00
分离接口和实现,把一个模块的函数,变量声明放到头文件中,具体实现也在同名.c 文件中。

算是规范吧,你不用头文件也是可以的。

稍大一点的项目,不同的开发者开发时只负责自己的一部分,但是需要知道其它部分的接口,这个时候就可以查看头文件,但不查看具体实现,避免过分依赖其他模块的实现。
LuckyKoala
2018-07-27 10:46:58 +08:00
https://gcc.gnu.org/onlinedocs/gcc-3.0.2/cpp_2.html

头文件有两个来源,一个是系统提供的接口,还有一个就是用户自定义的。

使用头文件还有一个好处就是所有一组的声明放在一个头文件中,外部通过 include 头文件来包含内容,需要修改声明的时候,修改一个头文件就行了,对应的引用这个头文件的地方也就应用修改了。
GPIO
2018-07-27 10:47:48 +08:00
码农翻身之前有篇文章叫《真正的程序员都应该搞清楚编译和链接》,本来想发链接给你的,不过要手机号验证所以作罢,你自己搜一下。
GeruzoniAnsasu
2018-07-27 10:50:09 +08:00
其实可以完全不需要头文件

你试试把需要的函数一个一个手动声明,也是照样可以正常链接的

但这不是,慢嘛,有能复用的声明干嘛要手动重写一遍?
thomaswang
2018-07-27 10:55:26 +08:00
@LuckyKoala 当 main 这个函数编译的时候,会有个 elf,里面有符号表,里面有一个符号指到其他模块的函数,这里面如何用到别的模块的函数定义,肯定是用了,但是不知道如何用的
Nitroethane
2018-07-27 10:59:36 +08:00
可以看看“深入理解计算机系统”的“链接”一章,讲得很清楚
LuckyKoala
2018-07-27 11:38:17 +08:00
@thomaswang https://en.m.wikipedia.org/wiki/Executable_and_Linkable_Format 这是 wiki 上对 elf 文件格式的介绍,至于如何链接的,你可以找找链接相关的文章看看。

系统学习的话,楼上提到的《深入理解计算机系统》我也很推荐。
thomaswang
2018-07-27 11:38:43 +08:00
@Nitroethane 看完,你没有发现,不需要引入.h 文件理论上也是可以链接的吗,可是为什么不能呢,引入的作用是什么呢
yksoft1
2018-07-27 11:48:06 +08:00
不要.h 里的声明的话 C 编译器不知道这个外部符号的类型(返回值),会默认设为 int 并产生警告。C++里面对于函数以外的符号没有声明,直接就是错误了。
LuckyKoala
2018-07-27 11:50:51 +08:00
@thomaswang 为什么不能?你怎么写的?

不包含头文件的话,自己加上需要的声明就可以了。

加入 “ int printf(const char *format, ...);” 就可以调用 printf
usufu
2018-07-27 11:51:28 +08:00
程序员的自我修养,这本书看完就知道了。
GeruzoniAnsasu
2018-07-27 12:05:30 +08:00
@thomaswang 不是 main 函数编译会有个 elf

每个.c 都会单独编译成待链接的.o 文件,其中包含本文件定义的强弱符号以及需要链接外部的未定义符号,所有未定义符号都会在链接阶段在所有链接文件中查找,并把对应的调用替换成实际函数地址
如果期望的某个未定义符号没有找到,一般就会报链接错误,但其实也可以用-undefined dynamic_lookup 强制所有未定义符号在运行期动态查找,标准库中的函数“自带这种定义”(不准确)
从原理上来说,main 函数其实也可以不用写的,只要 elf 文件头指定入口点就足够,但一般程序必须写 main 的原因是,编译器额外链接了某个.o,叫 /crt?+\.o/ 什么的,这个.o 自带一个 main 符号的引用,所以不写 main 链接这个.o 的时候查找 main 符号失败会像上面说的报链接错误,如果用参数选项控制不去链接额外的这个 crt 入口.o,就不一定需要 main 了,你可以再试试上面说的把 main 符号强制在运行期查找,会发现能链接出可执行文件,但这个程序你不 preload 一个带 main 的 lib 是跑不起来的

除了强弱符号,还有一类未定义符号,用 nm 查看类型是 U,这类符号会在 elf 被加载时由 ld.so 调用 dlresolve 在动态库中查找
GeruzoniAnsasu
2018-07-27 12:05:48 +08:00
------
emmmm 最后一段忘记删了
iceheart
2018-07-27 12:45:23 +08:00
1.编译过程
把 .c 源代码编译成机器码,名字是.o
2.链接过程
把编译好的各个.o 文件链接成一个可执行文件
3.
每个.c 编译过程都是独立互不相关的。也就是说,编译器编译 a.c 的时候编译器不知道有 b.c 存在,编译 b.c 的时候编译器不知道有 a.c 的存在。

基于以上关系说明,问题来了:
=> a.c 里使用了 b.c 里的一个函数,编译器编译 a.c 的时候不知道有 b.c,咋办?

编译器采取了一个办法:
1.使用一种约定,来描述 b.c 里边 a.c 要使用的函数,编译的时候根据约定生成调用指令。
2.让链接程序根据这些约定把 a.o,b.o 链接成可执行文件。
这个约定是什么?就是.h 头文件里的结构声明和函数声明。
thomaswang
2018-07-27 14:51:01 +08:00
@iceheart 多谢你来解我疑惑,你是很明白的
a.c 在编译阶段,不需要约定也是可以,每个.o 文件都有一个符号表,里面有自己的函数符号,也有调用的别人的函数符号,当链接的时候,每个.o 文件到其他所有的.o 文件找自己符号表里面的调外部的函数符号,这样就可以了,是吧
thomaswang
2018-07-27 15:10:11 +08:00
@iceheart

大佬,你人在上海不,请你吃顿饭啊,顺便和你聊聊技术和人生
zuoxiaomo
2018-07-27 15:55:03 +08:00
@thomaswang @iceheart 建议见面后互相给对方起一个名字。。。
thomaswang
2018-07-27 17:34:49 +08:00
@zuoxiaomo 何意

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/474567

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX