4

私はg++、特にリンク(C ++)オブジェクトファイルのいくつかの境界を理解しようとしてきました。質問する前に、できるだけ圧縮しようとした次の好奇心を見つけました。

コード

ファイルcommon.h

#ifndef _COMMON_H
#define _COMMON_H

#include <iostream>

#define TMPL_Y(name,T) \
struct Y { \
  T y; \
  void f() { \
    std::cout << name << "::f " << y << std::endl; \
  } \
  virtual void vf() { \
    std::cout << name << "::vf " << y << std::endl; \
  } \
  Y() { \
    std::cout << name << " ctor" << std::endl; \
  } \
  ~Y() { \
    std::cout << name << " dtor" << std::endl; \
  } \
}

#define TMPL_Z(Z) \
struct Z { \
  Y* y; \
  Z(); \
  void g(); \
}

#define TMPL_Z_impl(name,Z) \
Z::Z() { \
  y = new Y(); \
  y->y = name; \
  std::cout << #Z << "(); sizeof(Y) = " << sizeof(Y) << std::endl; \
} \
void Z::g() { \
  y->f(); \
  y->vf(); \
}

#endif

a.cppでコンパイルされたファイルg++ -Wall -c a.cpp

#include "common.h"

TMPL_Y('a',char);

TMPL_Z(Za);

TMPL_Z_impl('a',Za);

b.cppでコンパイルされたファイルg++ -Wall -c b.cpp

#include "common.h"

TMPL_Y('b',unsigned long long);

TMPL_Z(Zb);

TMPL_Z_impl('b',Zb);

main.cppコンパイルおよびリンクされたファイルg++ -Wall a.o b.o main.cpp

#include "common.h"

struct Y;
TMPL_Z(Za);
TMPL_Z(Zb);

int main() {
  Za za;
  Zb zb;
  za.g();
  zb.g();
  za.y = zb.y;
  return 0;
}

結果./a.out

a ctor
Za(); sizeof(Y) = 8
a ctor  // <- mayhem
Zb(); sizeof(Y) = 12
a::f a
a::vf a
a::f b  // <- mayhem
a::vf b // <- mayhem

質問

さて、私はリンクを試みて一緒g++にしようとするために私にいくつかの厄介な名前を呼ぶことを期待していました。特にの割り当ては悪です。それはまったく文句を言わないだけでなく、同じ名前()の互換性のない型をリンクさせたいだけでなく、 (resp。)の2次定義を完全に無視します。a.ob.oza.y = zb.yg++Yb.ob.cpp

私はこれまでのところフェッチされた何かをしていないことを意味します。2つのコンパイルユニットがローカルクラスに同じ名前を使用できることは非常に合理的です。大規模なプロジェクトで。

これはバグですか?誰かがこの問題に光を当てることができますか?

4

3 に答える 3

5

BjarneStroustrupの「C++プログラミング言語」の引用:

9.2リンケージ

関数、クラス、テンプレート、変数、名前空間、列挙子、および列挙子の名前は、ローカルとして明示的に指定されていない限り、すべての変換ユニットで一貫して使用する必要があります。

すべての名前空間、クラス、関数などが、それが表示されるすべての変換ユニットで適切に宣言され、同じエンティティを参照するすべての宣言が一貫していることを確認するのは、プログラマーの仕事です。[...]

于 2011-09-15T20:44:57.010 に答える
1

あなたの例では、Yの定義を次のような匿名の名前空間に入れることができます。

#define TMPL_Y(name,T) \
namespace { \
    struct Y { \
      T y; \
      void f() { \
        std::cout << name << "::f " << y << std::endl; \
      } \
      virtual void vf() { \
        std::cout << name << "::vf " << y << std::endl; \
      } \
      Y() { \
        std::cout << name << " ctor" << std::endl; \
      } \
      ~Y() { \
        std::cout << name << " dtor" << std::endl; \
      } \
    }; \
}

これにより、基本的に各コンパイルユニットに一意の名前空間が作成され、事実上、一意のYが作成され、リンカーは正しく関連付けることができます。

声明は

za.y = zb.y;

もちろん、2つのタイプには互換性がないため、これでも予測できない結果が生じます。

于 2011-09-15T22:04:07.757 に答える
0

多くの場合、C++コンパイラがキャッチする必要のないエラーがあります。それらの多くは、たとえば、一度に1つの翻訳単位を分析することによって検出することが不可能なエラーです。

たとえば、ヘッダーファイルで宣言するだけで、テンプレートを使用して複雑なケースを作成する必要はありません

void foo(int x);

次に、異なる変換単位で関数に2つの異なる定義を指定します。リンク時にエラーを出すために、C++コンパイラは必要ありません。

実際、同じ署名を持つグローバル関数を持つ2つの異なるヘッダーがあり、プロジェクトの一部が一方のヘッダーを使用し、プロジェクトの一部がもう一方のヘッダーを使用している可能性があるため、これが誤って発生することは明らかに不可能ではないことに注意してください。

Foo宣言が異なり、実装が異なる2つの異なるヘッダーファイルで特定のクラスを宣言した場合も、同じことが起こります。

この名前の乱用は、コンパイラがキャッチできる必要のない一種のエラーです。

于 2011-09-15T20:50:22.590 に答える