headers 中的字符串——这是否违反了 ODR?

Strings in headers -- does this violate the ODR?

考虑以下具有两个编译单元的程序。


// a.hpp

class A {
  static const char * get() { return "foo"; }
};

void f();

// a.cpp

#include "a.hpp"
#include <iostream>

void f() {
  std::cout << A::get() << std::endl;
}

// main.cpp

#include "a.hpp"
#include <iostream>

void g() {
  std::cout << A::get() << std::endl;
}

int main() {
  f();
  g();
}

出于某种原因需要创建全局字符串常量是很常见的。以完全天真的方式执行此操作会导致链接器问题。通常,人们将声明放在 header 中,将定义放在单个编译单元中,或者使用宏。

我的印象是这种使用函数的方式(如上所示)是 "okay",因为它是一个 inline 函数并且链接器消除了任何重复的副本产生,并且使用这种模式编写的程序似乎工作正常。但是,现在我怀疑它是否真的合法。

函数 A::get 在两个不同的翻译单元中是 odr-used,但它是隐式内联的,因为它是 class 成员。

[basic.def.odr.6] 中指出:

There can be more than one definition of a ... inline function with external linkage (7.1.2)... in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements. Given such an entity named D defined in more than one translation unit, then
- each definition of D shall consist of the same sequence of tokens; and
- in each definition of D, corresponding names, looked up according to 3.4, shall refer to an entity defined within the definition of D, or shall refer to the same entity, after overload resolution (13.3) and after matching of partial template specialization (14.8.3), except that a name can refer to a non-volatile const object with internal or no linkage if the object has the same literal type in all definitions of D, and the object is initialized with a constant expression (5.19), and the object is not odr-used, and the object has the same value in all definitions of D; and
- in each definition of D, corresponding entities shall have the same language linkage; and
- ... (more conditions that don't seem relevant)

If the definitions of D satisfy all these requirements, then the program shall behave as if there were a single definition of D. If the definitions of D do not satisfy these requirements, then the behavior is undefined.

在我的示例程序中,两个定义(每个翻译单元一个)分别对应相同的标记序列。 (这也是我原本觉得还可以的原因。)

但是,不清楚是否满足第二个条件。因为,名称 "foo" 可能与两个编译单元中的相同 object 不对应——它可能是每个编译单元中的 "different" 字符串文字,不是吗?

我尝试更改程序:

  static const void * get() { return static_cast<const void*>("foo"); }

以便它打印字符串文字的地址,我得到相同的地址,但是我不确定是否一定会发生。

它是否属于“...应指代 D 定义中定义的实体”? "foo" 是否被认为是在 A::get 内定义的?看起来可能是这样,但据我非正式理解,字符串文字最终会导致编译器发出某种全局 const char[] ,它位于可执行文件的特殊段中。 "entity" 被认为在 A::get 之内还是不相关?

"foo" 甚至被认为是 "name",还是术语 "name" 仅指有效的 C++ "identifier",就像可以用于变量或函数一样?一方面它说:

[basic][3.4]
A name is a use of an identifier (2.11), operator-function-id (13.5), literal-operator-id (13.5.8), conversion- function-id (12.3.2), or template-id (14.2) that denotes an entity or label (6.6.4, 6.1).

标识符是

[lex.name][2.11]
An identifier is an arbitrarily long sequence of letters and digits.

所以字符串文字似乎不是名称。

另一方面在第 5 部分

[expr.prim.general][5.1.1.1]
A string literal is an lvalue; all other literals are prvalues.

总的来说,我认为 lvalues 有名字。

你最后的说法是胡说八道。 "foo" 在语法上什至不是一个名称,而是一个 字符串文字 。字符串文字是左值,一些具有名称的左值并不意味着字符串文字是或具有名称。代码中使用的字符串文字不违反 ODR。

实际上,在 C++11 之前,TU 中内联函数的多个定义中的字符串文字指定同一实体,但多余且大部分未实现的规则已被 CWG 1823 删除。

Because, the name "foo" might not correspond to the same object in the two compilation units -- it's potentially a "different" string literal in each, no?

正确,但这无关紧要。因为 ODR 不关心具体的参数值。如果您确实设法以某种方式获得不同的例如在两个 TU 中都调用函数模板特化,这会有问题,但幸运的是字符串文字是无效的模板参数,所以你必须要聪明。