CポインタをNULLに初期化することは可能ですか?


90

私は次のようなものを書いていた

char *x=NULL;

という前提で

 char *x=2;

charアドレス2へのポインタを作成します。

しかし、GNU Cプログラミングチュートリアルではint *my_int_ptr = 2;、整数値2は、my_int_ptr割り当て時にランダムなアドレスに格納されると記載されています。

これは、自分char *x=NULLNULLキャストの値がcharメモリ内のランダムなアドレスに割り当てられていることを意味しているようです。

ながら

#include <stdlib.h>
#include <stdio.h>

int main()
{
    char *x=NULL;

    if (x==NULL)
        printf("is NULL\n");

    return EXIT_SUCCESS;
}

実際には、印刷します

無効です

コンパイルして実行するとき、未定義の動作、または少なくとも仕様不足の動作に依存していること、および

char *x;
x=NULL;

代わりに。


72
何をするかint *x = whatever;と何をするかの間には非常に混乱する違いがint *x; *x = whatever;あります。int *x = whatever;実際には同じように動作しint *x; x = whatever;ません、*x = whatever;
user2357112は2017

78
このチュートリアルでは、混乱を招く違いが間違っているようです。
user2357112は2017

51
Webには非常に多くのひどいチュートリアルがあります!すぐに読むのをやめてください。私たちは本当に、安っぽい本を公に恥ずかしく思うことができるSOブラックリストを必要としています...
Lundin

9
@MMこれは、2017年に不快感を少なくするものではありません。80年代以降のコンパイラとコンピュータの進化を考えると、18世紀に書かれた医者や医学の本を読んでいるのと基本的に同じです。
ランディン2017

13
私は「として、このチュートリアル格を考えていない... GNU Cチュートリアルのプログラミング」
marcelm

回答:


114

CポインタをNULLに初期化することは可能ですか?

TL; DRはい、とても。


ガイドに作られた実際の請求は同様に読み込み、

一方、単一の初期割り当てのみを使用する場合int *my_int_ptr = 2;、プログラムはが指すメモリ位置の内容をmy_int_ptr値2で埋めようとします。my_int_ptrはガベージで埋められるため、任意のアドレスにすることができます。[...]

まあ、彼ら間違っています、あなたは正しいです。

ステートメントについて(現時点では、整数変換へのポインターが実装定義の動作であることを無視して

int * my_int_ptr = 2;

my_int_ptr変数(へのポインタ型int)であり、独自のアドレス(型:整数へのポインタのアドレス)を持ち、そのアドレスに値を格納2ています。

さて、my_int_ptr、ポインタ型であること、私たちが言うことができ、それが指すメモリ位置に「タイプ」の値によって指さで開催された値my_int_ptr。したがって、基本的に、ポインタが指すメモリ位置の値ではなく、ポインタ変数の値割り当てています。

結論として、

 char *x=NULL;

ポインタ変数xNULL、ポインタが指すメモリアドレスの値ではなくに初期化します

これは、同じように

 char *x;
 x = NULL;    

拡張:

今、厳密に準拠しているので、

 int * my_int_ptr = 2;

制約違反があるため、これは違法です。明確にするために、

  • my_int_ptr ポインタ変数、型です int *
  • 整数定数は、定義により2typeを持ちintます。

これらは「互換性のある」タイプではないため、Lundinの回答で説明されている§6.5.16.1/ P1で説明されている単純な割り当ての規則に違反しているため、この初期化は無効です。

初期化がどのように単純な割り当て制約にリンクされているか興味がある場合は、引用C11、§6.7.9、P11

スカラーの初期化子は、オプションで中括弧で囲まれた単一の式でなければなりません。オブジェクトの初期値は式の初期値です(変換後)。単純な代入の場合と同じ型の制約と変換が適用され、スカラーの型はその宣言された型の非修飾バージョンになります。


@ Random832n彼ら間違っています。私は私の回答に関連部分を引用しましたが、そうでない場合は修正してください。ああ、そして意図的な強調。
Sourav Ghosh

「...制約違反が含まれているため、違法です。...整数リテラル、2は定義によりint型です。」問題があります。2はなのでint、割り当てに問題があるようです。しかし、それだけではありません。 NULLあってもよいですintint 0char *x = 0;明確に定義されているだけで、そうでchar *x = 2;はありません。6.3.2.3ポインタ3(BTW:Cが定義されていない整数リテラル、唯一の文字列リテラル化合物リテラル0ある整数定数
chux -回復モニカ

@chux正解ですchar *x = (void *)0;が、準拠しているのではないでしょうか。それとも値を生成する他の式でのみ0ですか?
Sourav Ghosh

10
@SouravGhosh:値を持つ整数定数0は特別です。これらは、一般的な整数式をポインター型に明示的にキャストするための通常の規則とは別に、暗黙的にnullポインターに変換します。
スティーブジェソップ2017

1
1974 Cリファレンスマニュアルで説明されている言語では、宣言で初期化式を指定できませんでした。そのような式がないため、「宣言ミラーの使用」がはるかに実用的になっています。構文int *p = somePtrExpressionは、値を設定しているように見えますが*p、実際にはの値を設定しているため、私見ではありませんp
スーパーキャット2017

53

チュートリアルが間違っています。ISO Cではint *my_int_ptr = 2;、エラーです。GNU Cでは、と同じ意味int *my_int_ptr = (int *)2;です。これ2は、コンパイラによって決定された何らかの方法で、整数をメモリアドレスに変換します。

そのアドレスによってアドレス指定された場所に何かを格納しようとはしません(もしあれば)。続けて書き込むと*my_int_ptr = 5;5そのアドレスでアドレス指定された場所に番号を保存しようとします。


1
整数からポインタへの変換が実装定義であることを知りませんでした。情報のおかげで。
taskinoor 2017

1
@taskinoorこの回答のように、キャストによって強制した場合にのみ変換があることに注意してください。キャストでない場合、コードはコンパイルされません。
ランディン2017

2
@taskinoor:はい、Cでのさまざまな変換はかなり混乱します。このQには、変換に関する興味深い情報があります。C:ポインター型間でキャストすると、未定義の動作ではありません。
sleske

17

チュートリアルが間違っている理由を明らかにするのint *my_int_ptr = 2;は「制約違反」です。これはコンパイルが許可されていないコードであり、コンパイラーはそれが発生したときに診断を提供する必要があります。

6.5.16.1に従って単純な割り当て:

制約

次のいずれかが保持されます。

  • 左側のオペランドはアトミック、修飾、または非修飾の算術型で、右側のオペランドは算術型です。
  • 左のオペランドは、構造体のアトミック、修飾、または非修飾バージョン、または右のタイプと互換性のある共用​​体タイプを持っています。
  • 左のオペランドはアトミック、修飾、または非修飾のポインター型を持ち、(左のオペランドが左辺値変換後に持つ型を考えると)両方のオペランドは互換性のある型の修飾または非修飾バージョンへのポインターであり、左が指す型はすべて権利が指すタイプの修飾子。
  • 左のオペランドがアトミック、修飾、または非修飾のポインター型を持ち、(左のオペランドが左辺値変換後に持つ型を考えると)1つのオペランドはオブジェクト型へのポインターであり、もう1つのオペランドは修飾または非修飾バージョンへのポインターvoid、および左でポイントされる型には、右でポイントされる型のすべての修飾子があります。
  • 左のオペランドはアトミック、修飾、または非修飾のポインターであり、右のオペランドはNULLポインター定数です。または
  • 左のオペランドのタイプはアトミック、修飾、または非修飾の_Boolで、右のオペランドはポインターです。

この場合、左のオペランドは非修飾ポインターです。正しいオペランドが整数(算術型)であることが許可されていることについては、どこにも言及されていません。したがって、コードはC標準に違反しています。

GCCは、標準のCコンパイラであると明示的に指定しない限り、正常に動作しないことがわかっています。コードをとしてコンパイルすると、-std=c11 -pedantic-errors正常に診断できるようになります。


4
-pedantic-errorsを提案したことに賛成。私はおそらく関連する-Wpedanticを使用しますが。
fagricipni 2017

2
正しいオペランドが整数であってはならないというステートメントの1つの例外:6.3.2.3項には、「値0の整数定数式、またはtype void *にキャストされるそのような式は、nullポインター定数と呼ばれます」とあります。見積もりの​​最後から2番目の箇条書きに注目してください。したがって、int* p = 0;を書くための合法的な方法int* p = NULL;です。後者はより明確で、より一般的ですが。
Davislor 2017

1
これにより、病理学的難読化もint m = 1, n = 2 * 2, * p = 1 - 1, q = 2 - 1;合法になります。
Davislor 2017

この回答の標準的な引用の箇条書き5でカバーされている@Davislor(後で要約でおそらく言及する必要があることに同意する)
MM

1
@chux整形式のプログラムではintptr_t、右側で許可されている型の1つに明示的に変換する必要があると思います。つまり、void* a = (void*)(intptr_t)b;ポイント4では有効ですが、(intptr_t)b互換性のあるポインター型でも、a void*でも、nullポインター定数でvoid* aもなく、算術型でもでもありません_Bool。標準では、変換は合法であると述べていますが、暗黙的なものではありません。
Davislor 2017

15

int *my_int_ptr = 2

整数値2を、割り当て時にmy_int_ptrにあるランダムアドレスに格納します。

これは完全に間違っています。これが実際に書かれている場合は、より良い本またはチュートリアルを入手してください。

int *my_int_ptr = 2アドレス2を指す整数ポインターを定義します2。アドレスにアクセスしようとすると、おそらくクラッシュします。

*my_int_ptr = 2、つまり行にがない場合、intランダムなアドレスmy_int_ptrが指しているものに値2を格納します。これを言って、NULLそれが定義されたときにポインターに割り当てることができます。char *x=NULL;完全に有効なCです。

編集:これを書いている間、整数からポインタへの変換が実装定義の動作であることを知りませんでした。詳しくは@MMと@SouravGhoshの良い回答をご覧ください。


1
それは制約違反であり、他の理由ではないため、完全に間違っています。特に、これは正しくありません:「int * my_int_ptr = 2は、アドレス2を指す整数ポインターを定義します」。
ランディン2017

@Lundin:「他の理由でない」というフレーズ自体が間違っていて、誤解を招くものです。型の互換性の問題を修正しても、チュートリアルの作成者がポインタの初期化と割り当てのしくみを著しく誤っているという事実が残ります。
オービットの軽さのレース

14

Cポインタに関する多くの混乱は、言語の構文の非常に悪い小さな選択によって裏付けられた、コーディングスタイルに関して最初に行われた非常に悪い選択から生じます。

int *x = NULL;正しいCですが、それは非常に誤解を招くものであり、私は無意味だとさえ言います。そしてそれは多くの初心者にとって言語の理解を妨げてきました。それは、後に私たちができること*x = NULL;はもちろん不可能だと考えるようになります。ご覧intのとおり、変数の型はでなく*x、変数の名前もでありません。また*、宣言内のは、と連携して機能的な役割を果たしません=。それは純粋に宣言的です。だから、もっと理にかなっているのはこれです:

int* x = NULL;これも正しいCですが、元のK&Rコーディングスタイルに準拠していません。これにより、型がint*であり、ポインター変数がxであることが完全に明らかになります。そのため、値NULLがにxポインターとして格納されていることを、初心者でも明らかですint

さらに、これはルールの導出を容易にします。スターが変数名から離れている場合、それは宣言ですが、名前に付けられているスターはポインター逆参照です。

だから、今ではさらに下には、我々はいずれかを実行できることをたくさんより理解なっx = NULL;たり*x = 2;、他の言葉で、それは簡単にどのように見るため、初心者になりvariable = expression、リードするpointer-type variable = pointer-expressiondereferenced-pointer-variable = expression。(初心者にとって、「式」とは「右辺値」を意味します。)

言語の構文での残念な選択は、ローカル変数を宣言するときにint i, *p;、整数と整数へのポインターを宣言することができるため*、が名前の有用な部分であると考えるようになるということです。しかし、そうではありません。この構文は、風変わりな特別なケースであり、便宜上追加されたものです。私の意見では、上に提案したルールが無効になるため、存在しないはずです。私の知る限り、この構文に意味があるのはこの言語の他のどこにもありませんが、たとえそうであっても、Cでのポインター型の定義方法の不一致を指しています。構造体メンバーなどでは、のtype* pointer-variable代わりにとしてポインターを宣言できますtype *pointer-variable。それは完全に合法であり、より理にかなっています。


int *x = NULL; is correct C, but it is very misleading, I would even say nonsensical,...同意しないことに同意する必要があります。It makes one think....考えるのをやめて、最初にCの本を読んでください。
Sourav Ghosh

^^これは完全に理にかなっているでしょう。だから主観的なものだと思います。
Mike Nakis 2017

5
@SouravGhosh意見の問題として、私はC int* somePtr, someotherPtr 2つのポインタを宣言するように設計されているべきだと思いint* somePtrます。
fagricipni 2017

1
@fagricipniこのため、複数変数宣言構文の使用をやめました。変数を1つずつ宣言します。本当に同じ行に配置したい場合は、コンマではなくセミコロンで区切ります。「場所が悪いなら、その場所に行ってはいけない。」
Mike Nakis

2
@fagricipniまあ、もし私がLinuxをゼロから設計できたら、のcreate代わりに使ったでしょうcreat。:)ポイントは、それがいかにあるかであり、それに適応するために自分自身を形作る必要があるということです。結局のところ、結局のところ個人的な選択に帰着します。
Sourav Ghosh 2017

6

多くの優れた答えに直角なものを追加したいと思います。実際、への初期化NULLは悪い習慣とはほど遠く、動的に割り当てられたメモリブロックを格納するためにそのポインターを使用する場合と使用しない場合がある場合に便利です。

int * p = NULL;
...
if (...) {
    p = (int*) malloc(...);
    ...
}
...
free(p);

ISO-IEC 9899標準に よると、free引数がの場合はnop NULLであるため、上記のコード(または同じ行でより意味のあるもの)は正当です。


5
CコードもC ++としてコンパイルする必要がある場合を除き、Cでmallocの結果をキャストするのは冗長です。

あなたは正しい、void*必要に応じて変換されます。しかし、CおよびC ++コンパイラーで動作するコードがあると、メリットがある場合があります。
Luca Citi

1
@LucaCiti CとC ++は異なる言語です。一方用に作成されたソースファイルを、もう一方用に設計されたコンパイラを使用してコンパイルしようとすると、エラーが発生するだけです。これは、Pascalツールを使用してコンパイルできるCコードを書き込もうとするようなものです。
Evil Dog Pie

1
いいアドバイス。私は(常に)ポインター定数を何かに初期化します。最近のCでは、これは通常それらの最終値であり、メディアresconst宣言さたポインターである可能性がありますが、ポインターを可変にする必要がある場合でも(ループで使用されるようにrealloc())、NULL以前に使用されていたバグをキャッチするように設定します実際の値が設定されています。ほとんどのシステムでNULLは、初期化されていないポインタに不要なものが含まれていて、そこに書き込むと任意のメモリが破損するのに対し、逆参照は失敗した時点でsegfaultを引き起こします(例外はあります)。
Davislor 2017

1
また、デバッガーでポインターにが含まれていることは非常に簡単NULLですが、ガベージポインターと有効なポインターを区別するのは非常に難しい場合があります。そのためNULL、宣言の瞬間から、すべてのポインタが常に有効またはのいずれかであることを確認すると役立ちます。
Davislor 2017


1

これは正しいです。

int main()
{
    char * x = NULL;

    if (x==NULL)
        printf("is NULL\n");

    return EXIT_SUCCESS;
}

この関数は、その機能に適しています。charポインターxに0のアドレスを割り当てます。つまり、ポインタxをメモリアドレス0にポイントします。

代替:

int main()
{
    char* x = 0;

    if ( !x )
        printf(" x points to NULL\n");

    return EXIT_SUCCESS;
}

あなたが欲しかったものについての私の推測は:

int main()
{
    char* x = NULL;
    x = alloc( sizeof( char ));
    *x = '2';

    if ( *x == '2' )
        printf(" x points to an address/location that contains a '2' \n");

    return EXIT_SUCCESS;
}

x is the street address of a house. *x examines the contents of that house.

「charポインタxに0のアドレスを割り当てます。」->多分。Cはポインターのを指定せず、それだけchar* x = 0; if (x == 0)が真になります。ポインタは必ずしも整数である必要はありません。
chux-モニカの復活2017年

「ポインタxがメモリアドレス0を指す」ことはありません。ポインター値を、0またはNULLと比較することでテストできる未指定の無効な値に設定します。実際の操作は実装定義です。ここには実際の質問に答えるものはありません。
ローンの侯爵
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.