如果在运行时缺失一个#include
会导致软件崩溃,而构建仍然可以继续吗?
换句话说,是否有可能出现这种情况:
#include "some/code.h"
complexLogic();
cleverAlgorithms();
和
complexLogic();
cleverAlgorithms();
两者都会成功构建,但会表现出不同的行为吗?
如果在运行时缺失一个#include
会导致软件崩溃,而构建仍然可以继续吗?
换句话说,是否有可能出现这种情况:
#include "some/code.h"
complexLogic();
cleverAlgorithms();
和
complexLogic();
cleverAlgorithms();
两者都会成功构建,但会表现出不同的行为吗?
是的,这完全是可能的。我相信有很多方法可以实现,但假设包含文件包含了一个调用构造函数的全局变量定义。在第一种情况下会执行构造函数,在第二种情况下则不会。
将全局变量定义放在头文件中是不好的编程风格,但这是可能的。
<iostream>
正是这样做的;如果任何翻译单元包括<iostream>
,则在程序启动时将构造std::ios_base::Init
静态对象,初始化字符流std::cout
等,否则不会。 - ecatmur是的,这是可能的。
有关#include
的所有内容都发生在编译时。但编译时的事情当然可以在运行时改变行为:
some/code.h
:
#define FOO
int foo(int a) { return 1; }
那么
#include <iostream>
int foo(float a) { return 2; }
#include "some/code.h" // Remove that line
int main() {
std::cout << foo(1) << std::endl;
#ifdef FOO
std::cout << "FOO" std::endl;
#endif
}
使用 #include
,重载决议可以找到更合适的 foo(int)
,因此打印出了 1
而不是 2
。并且,由于定义了 FOO
,它还会额外打印 FOO
。
这只是我立即想到的两个(无关的)示例,我相信还有很多其他的例子。只是为了指出微不足道的情况,预编译指令:
// main.cpp
#include <iostream>
#include "trouble.h" // comment this out to change behavior
bool doACheck(); // always returns true
int main()
{
if (doACheck())
std::cout << "Normal!" << std::endl;
else
std::cout << "BAD!" << std::endl;
}
然后
// trouble.h
#define doACheck(...) false
这可能有点病态,但我经历过类似的情况:
#include <algorithm>
#include <windows.h> // comment this out to change behavior
using namespace std;
double doThings()
{
return max(f(), g());
}
看起来无害。尝试调用std::max
。然而,windows.h将 max 定义为
#define max(a, b) (((a) > (b)) ? (a) : (b))
如果这是std::max
,那么这将是一个正常的函数调用,只计算f()和g()一次。但由于有windows.h,现在会对f()和g()进行两次评估:一次用于比较,一次用于获取返回值。如果f()或g()不具有幂等性,这可能会导致问题。例如,如果其中一个是返回每次都不同数字的计数器...
using namespace std;
并使用 std::max(f(),g());
,编译器将会捕获到这个问题(虽然错误信息可能比较晦涩,但至少会指向调用位置)。 - Ruslan二进制不兼容,访问成员变量或更糟的是调用错误类的函数:
#pragma once
//include1.h:
#ifndef classw
#define classw
class class_w
{
public: int a, b;
};
#endif
一个函数使用它,这样就可以了:
//functions.cpp
#include <include1.h>
void smartFunction(class_w& x){x.b = 2;}
引入另一个类的版本:
#pragma once
//include2.h:
#ifndef classw
#define classw
class class_w
{
public: int a;
};
#endif
在main函数中使用函数,第二个定义更改了类定义。这会导致二进制不兼容并在运行时崩溃。通过删除main.cpp中的第一个include来解决此问题:
//main.cpp
#include <include2.h> //<-- Remove this to fix the crash
#include <include1.h>
void smartFunction(class_w& x);
int main()
{
class_w w;
smartFunction(w);
return 0;
}
没有任何变体会产生编译或链接时错误。
反之,添加一个包含文件可以修复崩溃:
//main.cpp
//#include <include1.h> //<-- Add this include to fix the crash
#include <include2.h>
...
在修复旧版本程序的漏洞或使用外部库/dll/共享对象时,这些情况会更加困难。这就是为什么有时必须遵循二进制向后兼容性规则的原因。
可能会缺少模板特化。
// header1.h:
template<class T>
void algorithm(std::vector<T> &ts) {
// clever algorithm (sorting, for example)
}
class thingy {
// stuff
};
// header2.h
template<>
void algorithm(std::vector<thingy> &ts) {
// different clever algorithm
}
// main.cpp
#include <vector>
#include "header1.h"
//#include "header2.h"
int main() {
std::vector<thingy> thingies;
algorithm(thingies);
}
main.c
int main(void) {
foo(1.0f);
return 1;
}
foo.c
#include <stdio.h>
void foo(float x) {
printf("%g\n", x);
}
0
int foo(); // Has different meaning in C++
float
转换为 double
以进行传递。 因此,尽管我给出了 1.0f
,编译器会将其转换为 1.0d
以传递给 foo
。 根据 System V 应用程序二进制接口 AMD64 架构处理器补充规范,double
在 xmm0
的 64 个最低有效位中传递。但是,foo
需要一个 float
,并从 xmm0
的 32 个最低有效位中读取它,结果为 0。
#include <vld.h>
来将其集成到程序中。删除或添加VLD头文件不会“破坏”程序,但它会显著影响运行时行为。我见过VLD使程序变得如此缓慢,以至于无法使用。 - Haliburton