此代码:
void undefined_fcn();
void defined_fcn() {}
struct api_t {
void (*first)();
void (*second)();
};
api_t api = {undefined_fcn, defined_fcn};
定义了一个全局变量api
,其中包含一个指向不存在函数的指针。然而,它编译,令我惊讶的是,即使有所有-Wall -Wextra -Werror -pedantic
标志,也绝不会收到GCC的投诉链接。
此代码是共享库的一部分。只有当我加载库时,在运行时它才会失败。如何在图书馆链接时检查我是否忘记定义任何功能?
更新:this question提到同样的问题,答案是一样的:-Wl,--no-undefined
。 (顺便说一句,我想这甚至可以标记为重复)。但是,根据下面接受的答案,使用-Wl,--no-undefined
时应该小心。
答案 0 :(得分:3)
您无法让编译器告诉您是否忘记在该实现文件中定义该函数。原因是当你定义一个函数时,它在C ++中被隐式标记为extern
。在链接之后你无法分辨共享库中的内容(编译器的链接器不知道引用是否已定义)
如果您不熟悉extern
的含义。标记为extern
的事件表示外部链接,因此如果您有一个extern
变量,则编译器不需要该变量的定义位于使用它的转换单元中。定义可以在另一个实现文件中,并且在链接时解析引用(当您与定义变量的转换单元链接时)。这同样适用于函数,函数本质上是函数类型的变量。
要获得所需的行为,请使用函数static
告诉编译器该函数不是extern
并且是当前转换单元的一部分,在这种情况下必须定义它-Wundefined-internal
1}}接受这个(-Wundefined-internal
是-Werror
的一部分,所以只需编译)
答案 1 :(得分:3)
此代码是共享库的一部分。
这是关键。拥有共享库的整个目的是拥有一个不完整的"共享对象,带有未定义的符号,必须在主可执行文件加载它时解析,以及与之链接的所有其他共享库。那时,运行时加载程序尝试解析所有未定义的符号;并且必须解析所有未定义的符号,否则可执行文件将无法启动。
您说您正在使用gcc
,因此您可能正在使用GNU ld
。由于上述原因,ld
将链接共享库与未定义的符号,但无法链接可执行文件,除非所有未定义的符号都是针对可执行文件链接的共享库解析的。因此,在运行时,预期的行为是期望运行时加载程序也成功解析所有符号;因此,运行时加载程序无法启动可执行文件的唯一情况将表明致命的运行时环境失败(例如共享库被替换为不兼容的版本)。
有一些选项可用于覆盖此行为。 --no-undefined
选项指示ld
在链接共享库时报告未定义符号的链接失败,就像可执行文件一样。通过ld
间接调用gcc
时,会变为-Wl,--no-undefined
。
然而,你可能会发现这将是一个失败的主张。您最好希望共享库中的所有代码都不使用标准C ++或C库中的 任何 类。因为,猜猜怎么着? - 这些引用将是未定义的符号,您将无法链接您的共享库!
换句话说,这是你需要处理的必要之恶。