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 中都调用函数模板特化,这会有问题,但幸运的是字符串文字是无效的模板参数,所以你必须要聪明。
考虑以下具有两个编译单元的程序。
// 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 ofD
shall consist of the same sequence of tokens; and
- in each definition ofD
, corresponding names, looked up according to 3.4, shall refer to an entity defined within the definition ofD
, 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 ofD
, 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 ofD
; and
- in each definition ofD
, 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 ofD
. If the definitions ofD
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 中都调用函数模板特化,这会有问题,但幸运的是字符串文字是无效的模板参数,所以你必须要聪明。