作为C语言的老大哥——C++语言,伴随着C语言的成长而不断发展。即使到2025年的今天,C与C++混合使用的场景还是数不胜数。这不,一场C与C++混合使用而导致的C99标准宏冲突,就这样在不经意间发生。
一、背景
代码cpp.cpp在正常编译通过的情况下,在首行代码处插入一个新的标准C头文件c.h,c.h头文件就是包含了常用的C头文件,以及定义了一些公开接口,没有什么特别。然后再编译A.cpp,结果编译失败,提示代码中的SIZE_MAX找不到。
二、问题分析
代码中的SIZE_MAX是C99标准定义的宏,在stdint.h头文件中定义,详细如下:
#if !defined __cplusplus || defined __STDC_LIMIT_MACROS
……
# if __WORDSIZE == 64
# define SIZE_MAX (18446744073709551615UL)
# else
# define SIZE_MAX (4294967295UL)
# endif
# endif
……
#endif
cstdint头文件中关于stdint.h头文件的包含代码详细如下:
#if _GLIBCXX_HAVE_STDINT_H
# ifndef __STDC_LIMIT_MACROS
# define _UNDEF__STDC_LIMIT_MACROS
# define __STDC_LIMIT_MACROS
# endif
# ifndef __STDC_CONSTANT_MACROS
# define _UNDEF__STDC_CONSTANT_MACROS
# define __STDC_CONSTANT_MACROS
# endif
# include <stdint.h>
# ifdef _UNDEF__STDC_LIMIT_MACROS
# undef __STDC_LIMIT_MACROS
# undef _UNDEF__STDC_LIMIT_MACROS
# endif
# ifdef _UNDEF__STDC_CONSTANT_MACROS
# undef __STDC_CONSTANT_MACROS
# undef _UNDEF__STDC_CONSTANT_MACROS
# endif
#endif
c.h包含了头文件stdint.h,cpp.cpp包含了cpp.h,而cpp.h包含了cstdint。如果将c.h的位置移到cpp.h后面,则编译正常。
通过查看stdint.h头文件,定义SIZE_MAX有两个条件:其一是C代码,而cpp.cpp是C++代码,不满足该条件;其二是定义了宏__STDC_LIMIT_MACROS,而cpp.cpp首行包含c.h的情况下,实际先包含stdint.h头文件,此时未定义该宏而导致SIZE_MAX未定义,再次包含cpp.h而间接包含cstdint头文件时,由于stdint.h头文件前期已经被包含而导致二次包含时自动忽略,最终未定义宏__STDC_LIMIT_MACROS而导致代码编译报错。
如果将c.h位置移到cpp.h后面,由于先包含cstdint,所以会定义__STDC_LIMIT_MACROS宏,接着在包含stdint.h头文件时因为该宏的定义而定义SIZE_MAX宏,所以代码编译正常。
对于上述的分析,可以通过简单的一行代码进行对应测试验证,具体如下:
cat c.cpp
#include <stdint.h>
cat cpp.cpp
#include <cstdint>
g++ c.cpp -E -dD|grep SIZE_MAX
#define __SIZE_MAX__ (18446744073709551615UL)
g++ cpp.cpp -E -dD|grep SIZE_MAX
#define __SIZE_MAX__ (18446744073709551615UL)
#define SIZE_MAX (18446744073709551615UL)
其中,-E参数让编译器在预处理阶段之后停止,不进行编译、汇编和链接。输出的是预处理后的源代码(通常是.i文件或直接输出到标准输出)。-dD参数在预处理器的输出中,将所有的宏定义(包括预定义和用户定义的)以#define指令的形式输出。注意,这个选项会将宏定义插入到输出代码中对应的位置(或者按某种顺序输出)。
三、问题解决
对于C++使用C头文件的混用场景,只有一种办法实现最小化改动支持,那就是在c.h的头文件开始处(包含stdint.h头文件之前,最好在所有包含头文件之前,因为有些头文件内部会自动交叉包含)增加如下宏定义:
#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
#endif
是的,你没有看错,我们手动定义__STDC_LIMIT_MACROS宏。按照stdint.h和cstdint头文件的说明,都能够确保最终包含stdint.h头文件中SIZE_MAX宏被定义。
四、联系
如果有任何疑问欢迎随时联系交流!