是否有任何情况#include会在构建仍然进行的情况下丢失a 并在运行时破坏软件?
换句话说,是否有可能
#include "some/code.h"
complexLogic();
cleverAlgorithms();
和
complexLogic();
cleverAlgorithms();
会成功构建,但行为会有所不同?
#included 之后出现的代码的含义是很容易的。
是否有任何情况#include会在构建仍然进行的情况下丢失a 并在运行时破坏软件?
换句话说,是否有可能
#include "some/code.h"
complexLogic();
cleverAlgorithms();
和
complexLogic();
cleverAlgorithms();
会成功构建,但行为会有所不同?
#included 之后出现的代码的含义是很容易的。
Answers:
是的,这完全有可能。我敢肯定有很多方法,但是假设包含文件包含一个称为构造函数的全局变量定义。在第一种情况下,构造函数将执行,而在第二种情况下,则不会执行。
将全局变量定义放在头文件中的样式不佳,但是有可能。
是的,那是可能的。
关于#includes的所有事情都在编译时发生。但是,编译时,事情当然可以改变运行时的行为:
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());,编译器将抓住问题(带有模糊的消息,但至少指向调用站点)。
可能缺少模板专业化。
// 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);
}
二进制不兼容,访问成员甚至更糟糕,调用了错误的类的函数:
#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 /共享对象时,这些情况甚至更加困难。因此,有时必须遵循二进制向后兼容的规则。
我想指出的是,这个问题也存在于C语言中。
您可以告诉编译器函数使用某些调用约定。如果您不这样做,则编译器将不得不猜测它使用了默认值,这与C ++中的编译器可以拒绝对其进行编译不同。
例如,
main.c
int main(void) {
foo(1.0f);
return 1;
}
foo.c
#include <stdio.h>
void foo(float x) {
printf("%g\n", x);
}
在x86-64的Linux上,我的输出是
0
如果您在此处省略原型,则编译器会假设您已经
int foo(); // Has different meaning in C++
未指定参数列表的约定要求float应将其转换double为要传递的格式。因此,尽管我给出了1.0f,编译器仍将其转换1.0d为传递给foo。并且根据System V应用程序二进制接口AMD64体系结构处理器补充,double传递了的64个最低有效位xmm0。但是foo需要一个浮点数,它会从的32个最低有效位中读取该值xmm0,并得到0。