在阅读“C ++编程语言第4版”一书时,我正在深入研究地址常量表达式。它有一个描述地址常量表达式的短段:
静态分配对象的地址,例如全局 变量,是一个常数。但是,它的值由链接器分配, 而不是编译器,所以编译器无法知道值 这样的地址常数。这限制了常数的范围 指针和引用类型的表达式。例如:
constexpr const char* p1 = "asdf"; constexpr const char* p2 = p1; //OK constexpr const char* p2 = p1+2; //error: the compiler does not know the value of p1 constexpr char c = p1[2]; //OK, c=='d'; the compiler knows the value pointed to by p1
我有两个问题。
这个是相当简单的 - 因为编译器不知道静态对象的地址,那么它如何在编译期间评估第二个语句?毕竟,编译器不知道p1+2
的值这一事实意味着p1
首先必须是未知的,对吧?打开所有严格标志的g ++ 4.8.1接受所有这些语句。
如[{3}}中所示:
static constexpr int N = 3; int main() { constexpr const int *NP = &N; return 0; }
这里,NP被声明为地址常量表达式,即 指针本身就是一个常量表达式。 (这是可能的 通过将地址运算符应用于a来生成地址 静态/全局常量表达式。)
如果我们将N
简称为const
而没有constexpr
,那么这也会有用。但是,必须使用p1
显式声明constexpr
,以使p2
成为有效语句。否则我们得到:
错误:'p1'的值在常量表达式中不可用
为什么?据我所知,"asdf"
属const char[]
。
答案 0 :(得分:3)
N3485包含“地址常量表达式”
地址常量表达式是...指针类型的prvalue核心常量表达式(在上下文要求的转换之后),其值为具有静态存储持续时间的对象的地址....
字符串文字的第三个字符对象就是这样一个对象(参见2.14.5中的详细说明),而不是第一个字符对象。
请注意,此处没有使用变量,而是使用 object (因此我们可以访问数组元素以及类成员以获取地址常量表达式,前提是数组或类对象具有静态存储持续时间,并且访问不会违反核心常量表达式的规则。)
从技术上讲,链接器将在目标文件中进行重定位:
constexpr const char *x = "hello";
extern constexpr const char *y = x + 2;
我们将把它编译成目标文件并查看它的作用
[js@HOST1 cpp]$ clang++ -std=c++11 -c clangtest.cpp
[js@HOST1 cpp]$ objdump --reloc ./clangtest.o
./clangtest.o: file format elf32-i386
RELOCATION RECORDS FOR [.rodata]:
OFFSET TYPE VALUE
00000000 R_386_32 .L.str
[js@HOST1 cpp]$ objdump -s -j .rodata ./clangtest.o
./clangtest.o: file format elf32-i386
Contents of section .rodata:
0000 02000000 ....
[js@HOST1 cpp]$
链接器将获取该部分中已有的值,并将其添加到由重定位的“VALUE”属性引用的符号的值(通过该符号表示其在符号表中的地址)(在我们的我们添加了2
,因此Clang / LLVM在该部分中对2
进行了硬编码。
但是,必须使用constexpr显式声明p1才能使p2成为有效的语句。
这是因为你依靠它的价值,而不是它的地址,是不变的。一般情况下(见下文),您必须先将其标记为constexpr,以便此时编译器可以验证任何后续读取访问权限肯定可以依赖于获取常量。您可能希望如下更改它并查看它是否正常工作(我认为,因为对于整数和枚举类型的初始化const对象有特殊情况,您甚至可以从下面的p1
数组中读取在constexpr上下文中,即使没有将其标记为constexpr
。但是我的铿锵似乎拒绝了它。)
const char p1[] = "asdf";
constexpr const char *x = p1 + 2; // OK!
constexpr char y = p1[2]; // OK!