同一个变量在两个 .c 文件里类型不一致,程序会怎样?

Andrew Koenig《C 陷阱与缺陷(第二版)》4.5节

目录

为什么没人拦你

四种可能

结论

还有另一种"外部类型"


先看两个文件。

cpp 复制代码
/* a.c ------ 声明 n 是 int */
#include <stdio.h>
extern int n;

int main(void)
{
    printf("n = %d\n", n);
    return 0;
}
cpp 复制代码
/* b.c ------ 定义 n 是 long */
long n = 305419896;

一个变量叫 n,a.c 说它是 int,b.c 说它是 long。

这是一个无效的 C 程序。 但编译器不拦你,链接器不管,程序照样生成、照样能跑。

为什么没人拦你

书里说得很清楚:

这是一个无效的 C 程序,因为同一个外部变量名在两个不同的文件中被声明为不同的类型。然而,大多数 C 语言实现却不能检测出这种错误。 编译器对这两个不同的文件分别进行处理,这两个文件的编译时间甚至可以相差好几个月。因此,编译器在编译一个文件时,并不知道另一个文件的内容。连接器可能对 C 语言一无所知,因此它也不知道如何比较两个 n 的定义中的类型。

拆成两句:

  • 编译器一次只看一个文件。 编译 a.c 的时候,b.c 还没被打开过------它不会知道 n 在那边是什么类型?
  • 链接器只看符号名。 它在两个目标文件里各找到一个叫 n 的符号,把它俩接上,收工。类型?那是编译器的事,链接器不认得。

四种可能

书说运行时"存在很多的可能情况",列了四条:

1.编译器足够"聪明",检测到冲突,给你一条诊断消息。

2.int 和 long 的内部表示正好一样(书说尤其在 32 位机上)------程序碰巧正常工作,"本来错误的程序因为某种巧合却能够工作"。

类型 32 位系统 64 位系统
int 4 字节 4 字节
long 4 字节 8 字节

3.两者要求的存储大小不同,但共享存储空间的方式恰好对得上------也能工作。

也就指的是在int不溢出的情况下,int和long是生效的

4.共享方式使得给其中一个赋值,等于给另一个赋了个完全不同的值------程序不能正常工作。

这就是溢出的情况了,也是最坏的情况。


结论

因此,保证一个特定名称的所有外部定义在每个目标模块中都有相同的类型,一般来说是程序员的责任。而且,"相同的类型"应该是严格意义上的相同。

注意后半句:"严格意义上的相同"。书马上给了一个容易踩的例子:

cpp 复制代码
/* 文件 1 */
char filename[] = "/etc/passwd";

/* 文件 2 */
extern char* filename;

书说:

尽管在某些上下文环境中,数组与指针非常类似,但它们毕竟不同。......这两个对 filename 的声明使用存储空间的方式是不同的;它们无法以一种合乎情理的方式共存。

两张图一摆就明白了:

图 4.1 里 filename 就是那 12 个格子本身;

图 4.2 里 filename 是一个盒子,盒子里装着一个箭头。

名字一样,布局完全不是一回事。

详见 :char p\[\]= "hello";q = p; 复制的是字符串吗?C 的"举隅法"陷阱-CSDN博客

改法书上给了两条,任选一条,两边一致就行:

cpp 复制代码
char filename[] = "/etc/passwd";   /* 文件 1 */
extern char filename[];            /* 文件 2 */

或者:

cpp 复制代码
char* filename = "/etc/passwd";    /* 文件 1 */
extern char* filename;             /* 文件 2 */

还有另一种"外部类型"

书在结尾提了第二种:

有关外部类型方面,另一种容易带来麻烦的方式是忽略了声明函数的返回类型,或者声明了错误的返回类型。

这就是 4.4 节那个 sqrt 的例子。书补了一句规则:

C 语言中的规则是,如果一个未声明的标识符后跟一个开括号,那么它将被视为一个返回整型的函数。因此,这个程序完全等同于 extern int sqrt();。

详见:调用函数时老是有莫名其妙地错误?函数的形参实参与返回值-CSDN博客

到这里书讲完了。总结就是:

声明和定义必须严格同类型,这件事标准没派给编译器,派给了你。


相关推荐
小羊没烦恼!3 分钟前
异步编程:线程概述及使用
java·开发语言·mysql·算法·c#
·present·1 小时前
CSAPP(lecture 3)
c++
别动我齐刘海2 小时前
机器人 C++ / 系统联调补强2——异常处理、Protobuf
linux·c++·人工智能·学习·算法·机器学习·机器人
c#上位机2 小时前
C#上位机项目实战——exe项目调试更改dll中源代码
开发语言·c#
basketball6162 小时前
C++ 中字符串与常见类型的转换总结
开发语言·c++
一直C2 小时前
嵌入式 Linux 驱动开发学习笔记:内核定时器、platform 总线与 I²C 驱动
linux·驱动开发·嵌入式硬件·学习·imx6ull
等我调个Bug2 小时前
C++智能指针通俗讲解
开发语言·c++
承渊政道3 小时前
Linux网络学习【传输层协议UDP】
linux·网络·网络协议·学习·ubuntu·udp
andrsted3 小时前
练题簿免费刷题小程序:和学习搭子共用一份题库,一起刷题复习
学习·面试·微信小程序·小程序·学习方法·刷题
淡海水3 小时前
06-02-源码-增量标记算法实现
算法·unity·c#·gc