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++因为试图联系在一起而给我起一些讨厌的a.o名字b.o。尤其是分配za.y = zb.y是邪恶的。这不仅g++完全没有抱怨,而且我希望它将具有相同名称 ( Y) 的不兼容类型链接在一起,而且它完全忽略了b.o(resp. b.cpp) 中的次要定义。

我的意思是我没有做一些牵强附会的事情。两个编译单元可以为本地类使用相同的名称是很合理的,尤其是。在一个大项目中。

这是一个错误吗?有人可以对这个问题有所了解吗?

4

3 回答 3

5

引用 Bjarne Stroustrup 的“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;

当然,这仍然会产生不可预测的结果,因为这两种类型不兼容。

于 2011-09-15T22:04:07.757 回答
0

在许多情况下,存在不需要 C++ 编译器捕获的错误。例如,其中许多是无法通过一次分析一个翻译单元来检测的错误。

例如,如果您只是在头文件中声明,则无需使用模板制作复杂的案例

void foo(int x);

然后在不同的翻译单元中为函数提供两个不同的定义,C++ 编译器不需要在链接时给出错误。

请注意,这显然不是不可能发生错误的,因为实际上甚至可能存在两个具有相同签名的全局函数的不同标头,并且项目的一部分使用一个标头,而项目的一部分使用另一个标头。

Foo如果您在具有不同声明和不同实现的两个不同头文件中声明某个类,也会发生同样的情况。

这种滥用命名只是一种错误,编译器不需要能够捕获。

于 2011-09-15T20:50:22.590 回答