头文件可以防什么错?运用头文件做声明

参考:Andrew Koenig《C 陷阱与缺陷(第二版)》4.6节

目录

[书上的例子:file.h 和 file.c](#书上的例子:file.h 和 file.c)

[file.c 里其实有两个声明](#file.c 里其实有两个声明)

这样写的效果

直观演示


如果一个函数忘了声明,编译器把它当成返回 int 的函数照单全收,连接器对类型不匹配毫无反应。

类型错了没人管,谁来管?

书 4.6 开头给出答案:

每个外部对象只在一个地方声明。

这个地方一般就是一个头文件,所有用到它的模块都 include 这个头文件。

特别要指出的是,定义它的那个模块,也应该 include 这个头文件。

这条规则为什么能堵住这个洞?先看书是怎么用的

书上的例子:file.h 和 file.c

书里接着用 4.1 提过的 filename 例子:

一个多模块程序,每个模块都要知道同一个文件名,希望只改一处、所有模块同时更新。

第一步,建一个头文件 file.h,里面只有声明:

cpp 复制代码
extern char filename[];

第二步,所有用到 filename 的源文件,都加上:

cpp 复制代码
#include "file.h"

第三步,选一个源文件给出初值,不妨叫 file.c:

cpp 复制代码
#include "file.h"

char filename[] = "/etc/passwd";

file.c 里其实有两个声明

file.c 里的 include 展开,会看到 filename 出现了两次:

cpp 复制代码
extern char filename[];
char filename[] = "/etc/passwd";

一个声明,一个定义,堆在同一个文件里。这合法吗?

判定条件是两句:只要各声明是一致的,而且其中最多只有一个定义,这样写就合法。

extern char filename[]; 和 char filename[] = "/etc/passwd";

类型一致、定义只有一个------合法。


这样写的效果

书最后点出这样做为什么值:

头文件声明了 filename 的类型,于是每个 include 它的模块自动把类型声明对了;

file.c 定义了 filename,因为它也 include 了头文件,定义的类型自动和声明相符。

编译所有文件,filename 的类型就肯定是正确的。

声明的类型只有一份,谁也没机会抄错。


直观演示

故意让两边不一致------head.h 声明 n 是 int:

cpp 复制代码
extern int n;

conf.c 定义 n 是字符数组,但它 include 了头文件:

cpp 复制代码
#include "head.h"

char n[] = "xyz";

编译 conf.c,当场报错:

cpp 复制代码
conf.c(4): error C2040: "n":"char [0]"与"int"的间接寻址级别不同

两个冲突的声明出现在同一个编译单元里,编译器看得见两个,一比对就报 C2040。

错在编译期,程序根本没机会运行。

没头文件:同样的漂移,没人管。

把冲突拆到两个文件。d1.c:

cpp 复制代码
/* defines n as a char array */
char n[] = "xyz";

d2.c,一口咬定 n 是 int:

cpp 复制代码
#include <stdio.h>

extern int n;

int main(void)
{
    printf("n = %d\n", n);
    return 0;
}

编译 d1.c d2.c:零警告,链接通过,运行退出码 0,打印:

cpp 复制代码
n = 8026488

"xyz" 被当成了 8026488。这不是随机的垃圾数字------

"xyz\0" 在内存里是四个字节 78 79 7A 00,d2.c

按小端把前四个字节读成一个 int,就是 0x007A7978 = 8026488。每次运行都是这个数,地址会变,值不会。

这一组对照就是 4.6 的全部意义:

|--------------|------------------------------|
| | 声明漂移的结局 |
| 声明只有一份(头文件) | error C2040,编译期拦下 |
| 声明各抄一份(无头文件) | 编译零警告、链接通过,运行期读出 8026488 |


相关推荐
我命由我123451 小时前
Photoshop - Photoshop 使用工具
学习·职场和发展·产品运营·求职招聘·职场发展·产品经理·学习方法
外收内放1 小时前
Python基础语法练习题(53-54)
python·学习
by209992 小时前
list:迭代器与资源管理实现的综合叙述(下)
数据结构·c++·笔记·list
欣欣之王来了2 小时前
Python入门:Jupyter Notebook安装与使用指南
学习·架构·面向对象·项目·python教程
JWASX2 小时前
Java 转 go 学习 - kitex(2)
学习·golang
wdfk_prog2 小时前
LWIP教程 03:从 `low_level_input()` 到 `pbuf_free()`——`pbuf` 的数据视图、Chain 与引用计数
运维·网络·笔记·学习
MayZork2 小时前
Qt 开发集成CMake + vcpkg
开发语言·c++·qt
我命由我123452 小时前
Photoshop - Photoshop 设计概念
学习·职场和发展·产品运营·求职招聘·职场发展·产品经理·学习方法
书生执笔画浮沉2 小时前
Linux桌面端应用向鸿蒙PC迁移适配纪实
c++·qt·c·信创·海思·libc·ohos