71

驚いたことに、これは次のようにコンパイルされます。

const char* c_str()
{
    static const char nullchar = '\0';
    return nullchar;
}

そしてそれは私のコードにバグをもたらしました。ありがたいことに、私はそれを捕まえました。

これは C++ による意図的なものですか、それともコンパイラのバグですか? データ型が積極的に無視される理由はありますか?
Visual C++ 2010 とGCCで動作しましたが、明らかなデータ型の不一致を考えると、なぜ動作するのかわかりません。(これstaticも必要ありません。)

4

8 に答える 8

69

定義したようにnullchar、値が 0 の整数定数式です。

C++03 標準では、ヌル ポインター定数を次のように定義しています。簡単に言うと、 yournullcharは null ポインター定数です。つまり、暗黙的に変換して、基本的に任意のポインターに割り当てることができます。

ただし、暗黙的な変換が機能するには、これらすべての要素が必要であることに注意してください。たとえば、'\1'の代わりに を使用した'\0'場合、またはの修飾子を指定しなかった場合、暗黙的な変換は行われず、代入は失敗します。constnullchar

この変換を含めることは意図的ですが、望ましくないことは広く知られています。ヌル ポインター定数としての 0 は C から継承されました。Bjarne と C++ 標準委員会の残りのほとんど (および一般的な C++ コミュニティのほとんど) は、この特定の暗黙的な変換を削除することを心から望んでいると確信していますが、そうすることで、多くの C コードとの互換性が失われます (おそらくすべてに近い)。

于 2012-08-19T07:04:21.250 に答える
28

これは古い歴史です: C にさかのぼります。

C にはキーワードはありませんnull。C のヌル ポインター定数は次のいずれかです。

  • 0, 0L,のような値 0 の整数定数式(これは整数型である'\0'ことを思い出してください),char(2-4/2)
  • そのような式は、void*のよう(void*)0にキャストされます。(void*)0L(void*)'\0'(void*)(2-4/2)

NULL マクロ(キーワードではありません!) は、そのような null ポインター定数に展開されます。

最初の C++ 設計では、整数定数式のみが null ポインター定数として許可されていました。最近std::nullptr_t、C++ に追加されました。

C++ では、C ではなくconst、整数定数式で初期化された整数型の変数は、整数定数式です。

const int c = 3;
int i;

switch(i) {
case c: // valid C++
// but invalid C!
}

したがって、const char式で初期化された'\0'a は null ポインター定数です。

int zero() { return 0; }

void foo() {
    const char k0 = '\0',
               k1 = 1,
               c = zero();
    int *pi;

    pi = k0; // OK (constant expression, value 0)
    pi = k1; // error (value 1)
    pi = c; // error (not a constant expression)
}

そして、これは健全な言語設計ではないと思いますか?


C99 標準の関連部分を含むように更新されました... §6.6.6 によると...

整数定数式は整数型を持ち、整数定数、列挙定数、文字定数、sizeof 結果が整数定数である式、およびキャストの即値オペランドである浮動定数であるオペランドのみを持つ必要があります。整数定数式のキャスト演算子は、演算子のオペランドの一部を除いて、算術型を整数型にのみ変換しsizeof ます。

C++ のみのプログラマ向けのいくつかの説明:

  • C では、C++ プログラマーが「リテラル」として知っているものに対して「定数」という用語を使用します。
  • C++ では、sizeof常にコンパイル時の定数です。ただし、C には可変長配列があるため、コンパイル時の定数ではないsizeof場合があります。

次に、§6.3.2.3.3 の状態が表示されます...

値が 0 の整数定数式、または type にキャストされたそのような式 は、 null ポインター定数void *と呼ばれます。ヌル ポインター定数がポインター型に変換される場合、ヌル ポインターと呼ばれる結果のポインターは、任意のオブジェクトまたは関数へのポインターと等しくないことが保証されます。


この機能がどれほど古いかを確認するには、C99 標準の同一のミラーリングされたパーツを参照してください...

§6.6.6

整数定数式は整数型を持ち、整数定数、列挙定数、文字定数、sizeof結果が整数定数である式、およびキャストの即値オペランドである浮動定数であるオペランドのみを持つ必要があります。整数定数式のキャスト演算子は、演算子のオペランドの一部を除いて、算術型を整数型にのみ変換しsizeofます。

§6.3.2.3.3

値が 0 の整数定数式、または type にキャストされたそのような式 は、 null ポインター定数void *と呼ばれます。ヌル ポインター定数がポインター型に変換される場合、ヌル ポインターと呼ばれる結果のポインターは、任意のオブジェクトまたは関数へのポインターと等しくないことが保証されます。

于 2012-08-19T07:02:47.480 に答える
14

nullchar値0の(コンパイル時)定数式です。したがって、nullポインターへの暗黙的な変換は公正なゲームです。

詳細:ここでは1996 年のドラフト標準から引用しています。

char一体型です。 nullcharconst であるため、セクション 5.19.1 に従って、(コンパイル時の) 整数定数式です。

5.19 定数式 [expr.const]

1 いくつかの場所で、C++ は整数または列挙定数に評価される式を必要とします ... 整数定数式には以下が含まれます ... const 変数 ...

さらに、nullcharセクション 4.10.1 に従って、0 に評価され、暗黙的にポインターに変換されます。

4.10 ポインタ変換 [conv.ptr]

1 0 に評価される整数型の整数定数式 ( expr.const ) 右辺値 (NULL ポインター定数と呼ばれる) は、ポインター型に変換できます。

おそらく、これが許可される「理由」の直感的な理由 (私の頭のすぐ上) は、ポインターの幅が指定されていないため、任意のサイズの整数定数式から null ポインターへの変換が許可されるためです。


(新しい) C++03 標準の関連部分で更新されました... §5.19.1 によると...

整数定数式には、リテラル (2.13)、定数式 (8.5) で初期化された整数型または列挙型の列挙子、変数constまたは静的データ メンバー、整数型または列挙型の非型テンプレート パラメーター、およびsizeof式のみを含めることができます。

次に、§4.10.1 に目を向けます...

null ポインター定数は、0 に評価される整数型の整数定数式 (5.19) 右辺値です。null ポインター定数はポインター型に変換できます。結果はその型のnull ポインター値であり、オブジェクトへのポインターまたは関数型へのポインターの他のすべての値と区別できます。同じ型の 2 つのヌル ポインター値は、比較すると等しくなります。

于 2012-08-19T06:34:43.527 に答える
11

これがコンパイルされるのとまったく同じ理由でコンパイルされます

const char *p = 0; // OK

const int i = 0;
double *q = i; // OK

const short s = 0;
long *r = s; // OK

右側の式の型はintandshortですが、初期化されるオブジェクトはポインターです。これはあなたを驚かせますか?

C++ 言語 (および C) では、値を持つ整数定数式 (ICE)0は特別なステータスを持っています (ただし、ICE は C と C++ で異なる定義で定義されています)。これらはヌル ポインター定数として認定されます。それらがポインター コンテキストで使用されると、適切な型の null ポインターに暗黙的に変換されます。

型は整数型であり、このコンテキストとchar大差ありません。そのため、によって初期化されたオブジェクトは、C++ では null ポインター定数でもあります (ただし、C ではそうではありません)。intconst char0

ところで、boolC++ の型も整数型です。つまり、const bool初期化されたオブジェクトfalseも null ポインター定数です。

const bool b = false;
float *t = b; // OK

C++11 に対するその後の不具合報告では、null ポインター定数の定義が変更されました。修正後、null ポインタ定数は「値ゼロの整数リテラルまたは std::nullptr_t 型の prvalue」のみとなります。上記のポインターの初期化は、修正後、C++11 では整形式ではなくなりました。

于 2012-08-19T07:05:52.723 に答える
6

データ型を無視していません。バグではありません。そこに入れた const を利用して、その値が実際には整数 0 であることを確認しています (char は整数型です)。

整数 0 は有効な (定義による) null ポインター定数であり、ポインター型に変換できます (null ポインターになります)。

ヌル ポインターが必要な理由は、「どこも指していない」チェック可能なポインター値を持つためです (つまり、ヌル ポインターを整数 0 と比較すると、true が返されます)。

const を削除すると、エラーが発生します。そこに double を入れると (他の多くの非整数型と同様に、[変換演算子のオーバーロードによって] const char* に変換できる型だけが例外だと思います)、エラーが発生します (w/o がなくても)。定数)。などなど。

全体として、この場合、null ptr 定数を返していることが実装で認識されます。ポインタ型に変換できます。

于 2012-08-19T07:09:40.950 に答える
5

この質問に対する本当の答えの多くは、コメントで終わったようです。要約する:

  • C++ 標準ではconst、整数型の変数を「整数定数式」と見なすことができます。なんで?C ではマクロと列挙型のみが整数定数式の場所を保持できるという問題を回避する可能性が非常に高いです。

  • (少なくとも) C89 までさかのぼると、値 0 の整数定数式は暗黙的に (任意の型の) null ポインターに変換可能です。そして、これは C コードでよく使用さNULL#defineます(void*)0

  • K&R に戻ると、0null ポインターを表すためにリテラル値が使用されています。この規則は、次のようなコードであらゆる場所で使用されます。

    if ((ptr=malloc(...)) {...} else {/* error */}
    
于 2012-08-19T07:49:13.617 に答える
2

オートキャストがあります。このプログラムをうまく実行した場合:

#include <stdio.h>
const char* c_str()
{
    static const char nullchar = '\0';
    return nullchar;
}

int main()
{
    printf("%d" , sizeof(c_str()));
    return 0;
}

私のコンピューターでは、出力は4になります->ポインターのサイズ。

コンパイラは自動キャストします。注意、少なくともgccは警告を出します(VSについては知りません)

于 2012-08-19T06:38:29.593 に答える
2

null文字がタイプ間で共通しているという事実かもしれません。あなたがしているのは、ヌル文字を返すときにヌル ポインターを設定することです。文字のアドレスをポインターに渡すのではなく、文字の値を渡すため、他の文字が使用された場合、これは失敗します。Null は有効なポインターおよび文字値であるため、null 文字をポインターとして設定できます。

つまり、 null は、配列、ポインター、変数のいずれであるかに関係なく、空の値を設定するために任意の型で使用できます。

于 2012-08-19T06:40:34.430 に答える