#includeビルドがまだ行われているときに、実行時にソフトウェアが壊れるケースはありますか?
つまり、
#include "some/code.h"
complexLogic();
cleverAlgorithms();
そして
complexLogic();
cleverAlgorithms();
両方とも正常にビルドされますが、動作は異なりますか?
#includedの後に続くコードの意味を完全に変更するマクロをヘッダーに定義するのは非常に簡単です。
#includeビルドがまだ行われているときに、実行時にソフトウェアが壊れるケースはありますか?
つまり、
#include "some/code.h"
complexLogic();
cleverAlgorithms();
そして
complexLogic();
cleverAlgorithms();
両方とも正常にビルドされますが、動作は異なりますか?
#includedの後に続くコードの意味を完全に変更するマクロをヘッダーに定義するのは非常に簡単です。
回答:
はい、それは完全に可能です。方法はたくさんあると思いますが、インクルードファイルにコンストラクターを呼び出すグローバル変数定義が含まれているとします。最初のケースではコンストラクターが実行され、2番目のケースでは実行されません。
ヘッダーファイルにグローバル変数の定義を置くのはスタイルが悪いですが、それは可能です。
<iostream>標準ライブラリでは正確にこれを行います。変換ユニットに含まれ<iostream>てstd::ios_base::Initいる場合、プログラムの開始時に静的オブジェクトが作成され、文字ストリームstd::coutなどが初期化されます。それ以外の場合は作成されません。
はい、可能です。
#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ます。
それはすぐに頭に浮かんだ2つの(無関係の)例にすぎません。もっとたくさんあると思います。
ささいなケースを指摘するために、プリコンパイラ指令:
// 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は最大値を
#define max(a, b) (((a) > (b)) ? (a) : (b))
これがの場合、これはstd::max、f()とg()を1回評価する通常の関数呼び出しになります。しかし、windows.hがあると、f()またはg()が2回評価されます。1回は比較中に、もう1回は戻り値を取得します。f()またはg()がべき等でなかった場合、これは問題を引き起こす可能性があります。たとえば、そのうちの1つがたまたまカウンターであり、毎回異なる数値を返す場合などです。
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の関数を使用して、2番目の定義はクラス定義を変更します。バイナリの非互換性が発生し、実行時にクラッシュするだけです。そして、main.cppの最初のインクルードを削除して問題を修正します。
//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);
}
Linux on x86-64では、私の出力は
0
ここでプロトタイプを省略すると、コンパイラーは、
int foo(); // Has different meaning in C++
また、未指定の引数リストの規約では、それfloatをdouble渡すために変換する必要があります。だから私は与えた1.0fが、コンパイラはそれを1.0dに変換してに渡しfooます。また、System VアプリケーションバイナリインターフェイスAMD64アーキテクチャプロセッササプリメントによればdouble、は64の最下位ビットで渡されますxmm0。しかしfoo、floatを期待し、それを32の最下位ビットから読み取りxmm0、0を取得します。