• Makefile 的用处,解决已包含头文件但还是 undefined reference to


    A.c (main函数)

    #include "B.h"
    
    int main(void) {
          //内容
          return 1;
    }
    

    B.c

    #include "B.h"
    void func_b() {
          //内容
    }
    

    B.h

    #ifndef B_H_
    #define B_H_
    
    //头文件内容
    void func_b();
    #endif
    

    注:B_H_ 是规范的写法,_B_H 不是规范写法,因为c库内置的定义都是下划线开头的,用户定义的头文件不应该以下划线开头

    好了,我们编译一下(Linux 下的可执行文件可以没有后缀名,而 Windows 下的可执行文件需要 exe 后缀,即 A.exe)

    gcc A.c -o A
    

    这时会提示 undefined reference to func_b

    为什么呢,我们看一下编译工具预处理后但还没汇编的源码文件

    gcc -E A.c -o A.i
    

    这时候你可以看到 A.i 里面的内容,只有 A.c 和 B.h 的内容以及其他链接信息,但没有包含 B.c 的内容。
    因为我们的编译命令错了

    正确的编译命令

    gcc A.c B.c -o A
    

    单文件的编译流程:
    hello.c(预处理) -> hello.i(编译) -> hello.s(汇编) -> hello.o(链接) -> hello

    gcc -E    hello.c -o hello.i   //预处理
    gcc -S    hello.i -o hello.S   //编译成汇编码
    gcc -c    hello.s -o hello.o   //汇编成二进制
    gcc       hello.o -o hello     //链接
    

    多文件就要生成多个.o文件,然后用以下命令链接起来

    gcc 1.o 2.o 3.o -o hello
    

    当然如果目录里只放需要的文件,也可以使用

    gcc *.c -o hello
    

    但是文件多了之后,只放需要的文件就几乎不可能了,何况还有子目录呢

    由于文件过多,所以后来人们使用专用的脚本来处理
    其中 Makefile Cmake 等是比较受欢迎的

    下面放个例子
    目录结构

    $ tree 
    .
    ├── bit_bmp.h
    ├── bmp
    │   ├── 000.bmp
    │   ├── 1.bmp
    │   ├── 2.bmp
    │   ├── 3.bmp
    │   ├── 4.bmp
    │   ├── 5.bmp
    │   └── background.bmp
    ├── Makefile
    ├── my_graph (生成的可执行文件)
    ├── my_graph.c
    ├── my_graph.o  (用于连接的Object)
    ├── mylib
    │   ├── graph.c
    │   ├── graph.h
    │   └── graph.o  (用于连接的Object)
    ├── show_bmp2.c
    ├── state
    └── state.c
    
    2 directories, 18 files
    


    效果如 make 后输出的日志一样,当然你复制make后的日志执行也是一样的,但是只打 make 命令就可以完成多爽啊,脚本的魅力就在于此

    语法详解:
    前面的 := 都是变量定义

    解释一下第10行到第17行
    产物名: 原料1名 原料2名
    命令(如果原料更新了则执行此处)

    如 第13行

    graph.o: mylib/graph.c mylib/graph.h
    	$(CC) -c mylib/graph.c -o mylib/graph.o
    

    产物graph.o: 原料mylib/graph.c mylib/graph.h
    如果原料更新了就执行命令 $(CC) -c mylib/graph.c -o mylib/graph.o

    注意:其中原料名必须和其依赖的产物名一致。

    思考:命令和冒号前都要标注产物文件名,那岂不是很多余?

    那我们用上通配符符号:%
    

    $@ 表示目标文件
    $< 表示第1个依赖文件
    $^ 表示所有依赖文件

    好,改写一下我的 Makefile

    Ps. Makefile 会根据文件是否更新而决定是否编译某个文件

    另外

    不要在头文件里定义函数或变量,头文件里应该只有声明
    因为c语言里,可以重复声明,但不能重复定义。

    当头文件多次包含时,就会导致重复定义,这个问题是 #ifndef #endif 条件宏也无法解决的(因为c语言处理头文件包含就是把整个头文件合并到一个文件里,而条件宏只对单个Object文件有效,多个Object文件连接后里就会导致很多重复的地方)

    所以头文件里只应该有声明,而不应该有定义。

    正确做法:
    在 c 文件里,定义全局变量如 int your_value = 0;
    在 h 头文件里,声明外部全局变量 extern int your_value;

    另外,头文件里应该只放对外部有用的东西,或者方便修改的宏。仅对头文件同名的 c 文件有效的声明或宏,应该只放在 c 文件里。

    总结

    当出现如 undefined reference to `album' 时,
    检查如下:

    1. 是否编译了所需要的全部 .c 或.o 文件

    当出现如
    my_graph.o:(.data+0x18): multiple definition of `MUSIC'
    state.o:(.data+0x18): first defined here 时,
    检查如下:

    1. 头文件里是否有定义 (应该只有声明,不应该有定义)

    其他

    在C语言中,数组是不可拷贝的,因此当数组作为参数传递时,会退化成指针,所以sizeof宏得到的是一个指针的大小 [知乎]
    函数内,参与运算的形参是实参的拷贝。而拷贝过程只发生了值传递,既传递了数组的地址,而把大小丢了。实参既数组本身你可以理解为一个地址加一个类型加一个大小。而形参就剩个地址了。这就是数组名作函数入参会退化为指针。[知乎]

  • 相关阅读:
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    每天一点点之 taro 框架开发
    每天一点点之 taro 框架开发
    每天一点点之 taro 框架开发
    WIN10怎么查看端口,并杀死进程
    每天一点点之 taro 框架
    taro编译的时候报 exports.pRimraf = util_1.promisify(rimraf); 错误
    新部署到服务器 报 The requested URL /home/profession was not found on this server. 错误
    ubuntu16.04 重置mysql密码
    SQLSTATE[HY000] [2002] No such file or directory
  • 原文地址:https://www.cnblogs.com/yucloud/p/Makefile.html
Copyright © 2020-2023  润新知