3

「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

2 つの質問があります。

  1. これはかなり些細なことです。コンパイラは静的オブジェクトのアドレスを知らないため、コンパイル時に 2 番目のステートメントをどのように評価できるのでしょうか。結局、コンパイラが の値を知らないという事実は、そもそもそれが不明でなければならないp1+2ことを意味しp1ますよね? ただし、すべての厳格なフラグがオンになっている g++ 4.8.1 は、これらすべてのステートメントを受け入れます。

  2. このトピックで例示されているように:

static constexpr int N = 3;
int main()
{
  constexpr const int *NP = &N;
  return 0;
}

ここで、NP はアドレス定数式、つまりそれ自体が定数式であるポインタとして宣言されています。(これは、静的/グローバル定数式にアドレス演算子を適用してアドレスを生成する場合に可能です。)

Nこれは、単純constに withoutとして宣言した場合にも機能しconstexprます。ただし、有効なステートメントであるためには、p1を使用して明示的に宣言する必要があります。それ以外の場合は次のようになります。constexprp2

エラー: 'p1' の値は定数式では使用できません

何故ですか?"asdf"私のconst char[]知る限りです。

4

1 に答える 1

5

N3485には「アドレス定数式」について記載されています

アドレス定数式は、静的記憶域期間を持つオブジェクトのアドレスに評価される ... ポインター型の prvalue コア定数式 (コンテキストで必要な変換後) です ....

文字列リテラルの 3 番目の文字オブジェクトはそのようなオブジェクト (2.14.5 の詳細を参照) であり、最初の文字オブジェクトよりも少なくありません。

variableは使用されませんが、ここでは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/LLVM2はセクションで a をハードコードしました)。

ただし、p2 を有効なステートメントにするためには、constexpr を使用して p1 を明示的に宣言する必要があります。

これは、アドレスではなく値が定数であることに依存しているためです。一般に (以下を参照)、事前に constexpr としてマークする必要があります。これにより、その時点でコンパイラーは、その後の読み取りアクセスが確実に定数の取得に依存できることを検証できます。次のように変更して、動作を確認することもできます (整数型および列挙型の初期化された const オブジェクトには特別なケースがあるため、マークされていなくても、constexpr コンテキストで以下の配列から読み取ることもできると思います。ただし、私のクランはそれを拒否しているようです)p1constexpr

const char p1[] = "asdf";
constexpr const char *x = p1 + 2; // OK!
constexpr char y = p1[2]; // OK!
于 2013-07-15T17:38:49.047 に答える