型の後ではなく、変数名の前にアスタリスクがあるのはなぜですか?


187

ほとんどのCプログラマーが変数に次のような名前を付けるのはなぜですか。

int *myVariable;

このようにするのではなく:

int* myVariable;

どちらも有効です。アスタリスクは型の一部であり、変数名の一部ではないようです。誰もがこの論理を説明できますか?


1
一般的に、2番目のスタイルはより直感的に見えますが、前者はコード内の型関連のバグを回避する方法です。あなたが本当に後者のスタイルに夢中なら、いつでもに行くことができますがtypedefs、それは不必要な複雑さ、IMHOを追加します。
2016

回答:


251

それらはまったく同じです。ただし、

int *myVariable, myVariable2;

myVariableの型がint *であるのは明らかなようですが、myVariable2の型はintです。に

int* myVariable, myVariable2;

どちらもint *型であることは明白に思えるかもしれませんが、それはintmyVariable2型なので正しくありません

したがって、最初のプログラミングスタイルはより直感的です。


135
たぶん、私は1つの宣言で型を混ぜて一致させません。
BobbyShaftoe 2008

15
@BobbyShaftoe同意する。ここですべての議論を読んだ後でも、私はint* someVar個人的なプロジェクトに固執しています。それはもっと理にかなっています。
Alyssa Haroldsen、2014

30
@Kupiakos「宣言は使用に従います」に基づいたCの宣言構文を学ぶまで、それはより意味があります。宣言は、同じ型の変数を使用する場合とまったく同じ構文を使用します。intの配列を宣言すると、次のようにはなりませんint[10] x。これは単にCの構文ではありません。文法はとしてint (*x)ではなく、として明示的に解析する(int *) xため、アスタリスクを左側に配置することは単に誤解を招くものであり、C宣言構文の誤解に基づいています。
ピーカー、2014

8
訂正:したがって、1行に複数の変数を宣言しないでください。一般に、他の無関係で悪質で危険なコーディングスタイルに基づいて特定のコーディングスタイルを動機付けるべきではありません。
ランディン

6
これが、ポインタ宣言ごとに1つの変数を使用する理由です。int* myVariable; int myVariable2;代わりに行っても、まったく混乱はありません。
Patrick Roberts

122

別の見方をすると、*myVariableはタイプintであり、ある程度の意味があります。


6
これは私のお気に入りの説明であり、Cの宣言の癖を一般的に説明しているため、うまく機能します。
ベンジャミンポラック

12
実際のポインタ型がないと想像できるので、それは一種の見事なものです。適切に参照または逆参照されると、プリミティブ型の1つを提供する変数のみがあります。
biozinc 2008

1
実際には、「* myVariable」はNULL型である可能性があります。さらに悪いことに、それは単なるランダムなメモリ位置である可能性があります。
qonf 2012年

4
qonf:NULLはタイプではありません。myVariableNULLにすることもできます。その場合*myVariable、セグメンテーション違反が発生しますが、タイプNULLはありません。
Daniel Roethlisberger、2013年

28
この点は、そのようなコンテキストでは誤解を招く可能性があります。int x = 5; int *pointer = &x;それは、int *pointerpointerそれ自体ではなく、ある値に設定することを示唆しているためです。
rafalcieslak 2013

40

その行の*は、型よりも変数に密接にバインドしているためです。

int* varA, varB; // This is misleading

@Lundinが以下に指摘するように、constはさらに検討する必要がある微妙な点を追加します。行ごとに1つの変数を宣言することで、これを完全に回避できます。

int* varA;
int varB;

明確なコードと簡潔なコードのバランスをとるのは難しく、12行の冗長な行int a;も適切ではありません。それでも、私はデフォルトで1行につき1つの宣言を行い、後でコードを結合することについて心配しています。


2
まあ、誤解を招く最初の例は、私の目には設計エラーです。できれば、その宣言方法をCから完全に削除して、両方ともint *型になるようにしました。
Adam Bajger、

1
「*は型よりも変数に密接にバインドします」これは素朴な議論です。考えてくださいint *const a, b;。*「バインド」はどこにありますか?の型aint* constなので、*が型自体の一部である場合、*は変数に属しているとどうして言えますか?
ランディン

1
質問に固有、またはすべてのケースをカバー:1つ選択します。私はメモしておきますが、これは私の最後の提案に対するもう1つの良い議論です。1行につき1つの宣言は、これを台無しにする機会を減らします。
ojrac

36

これまで誰もここで触れていないことは、このアスタリスクは実際にはCの「逆参照演算子」であるということです。

*a = 10;

上記の行は、私が割り当てたいという意味ではありません10a、それは私が割り当てること10何でもメモリ位置へaのポイント。そして、私は誰も書いているのを見たことがありません

* a = 10;

あなたはいますか?したがって、間接参照演算子は、ほとんど常にスペースなしで記述されます。これはおそらく、複数行にまたがる乗算と区別するためです。

x = a * b * c * d
  * e * f * g;

ここに *eは誤解を招くでしょうね。

さて、次の行は実際にはどういう意味ですか?

int *a;

ほとんどの人は言うでしょう:

それは値aへのポインタであることを意味しintます。

これは技術的に正しく、ほとんどの人はそれをそのように見たり読んだりすることを好み、それが現代のC標準がそれを定義する方法です(C言語自体がすべてのANSIおよびISO標準に先行していることに注意してください)。しかし、それを見る唯一の方法ではありません。この行は次のように読むこともできます。

の逆参照された値のaタイプはintです。

したがって、実際には、この宣言のアスタリスクは、その配置を説明する逆参照演算子としても見なされます。そして、それaはポインタが実際にまったく宣言されていないことであり、実際に逆参照できるのはポインタだけであるという事実によって暗黙的です。

C標準では、*演算子に対して2つの意味のみを定義しています。

  • 間接演算子
  • 乗算演算子

そして、間接参照は単一の意味に過ぎず、ポインターを宣言するための特別な意味はありません。間接参照だけがあり、これは間接参照操作が行うものであり、間接アクセスを実行するので、int *a;このようなステートメント内でも間接アクセス*手段したがって、上記の2番目のステートメントは、最初のステートメントよりもはるかに標準に近いものです。


6
ここに別の答えを書いてくれないようにしてくれてありがとう。ところで、私は通常、int a, *b, (*c)();「次のオブジェクトを次のように宣言しますint:オブジェクトa、が指すオブジェクトb、およびが指す関数から返されたオブジェクト」のようなものとして読みますc
Antti Haapala、2016年

1
オペレータはなく、間接参照されていない(でもまだ定義されていない)*int *a;a
MM

1
@MM ISO C標準のページと行番号を指定してください。この標準では、アスタリスクは乗算や間接参照以外の何かである可能性があると記載されています。例として「ポインタ宣言」を示しているだけで、アスタリスクの3番目の意味はどこにも定義されていません(また、演算子ではないという意味は定義されていません)。ああ、私はどこにも「定義されている」とは主張していません。または、Jonathan Lefflerが述べたように、C標準では、*は常に「文法」であり、リストされた宣言指定子の一部ではありません(したがって、宣言の一部ではないため、演算子である必要があります)
Mecki

1
@MM 6.7.6.1はまさに私が言ったように、それは与えない、例によって宣言を示して意味をする*(それがないように、合計式にのみ意味を与える*表現内で!)。「int a;」*aと書いてあります ポインタを宣言しますが、それがそうであると主張することは決してありませんが、に与えられた意味がなければ、それを*の逆参照された値はint である読み取りますが、それは同じ事実上の意味を持つので、完全に有効です。6.7.6.1で書かれたものは何も、実際には何もそのステートメントに矛盾しません。
Mecki

2
「トータル表現」はありません。 int *a;式ではなく宣言です。aによって逆参照されませんint *a;。 処理さaれている時点で*はまだ存在していません。int *a = NULL;nullポインターを逆参照するため、バグだと思いますか?
MM

17

私はここで外に出て、変数の宣言とパラメータと戻り値の型の両方について、この質問に対する直接の回答があると言います。つまり、アスタリスクは名前の隣に置く必要がありますint *myVariable;。理由を理解するには、Cで他のタイプのシンボルを宣言する方法を見てください。

int my_function(int arg); 関数の場合。

float my_array[3] 配列の場合。

呼ばれる一般的なパターン、宣言は使用を以下のは、シンボルのタイプは名前の前の部分に分割され、かつ部品ということで周りの構文模倣名の前後名、およびこれらの部品は、あなたが取得するために使用します左側のタイプの値:

int a_return_value = my_function(729);

float an_element = my_array[2];

そして、: int copy_of_value = *myVariable;

C ++は、参照を使用する作業でスパナをスローします。これは、参照を使用する時点での構文が値型の構文と同じであるため、C ++がCに対して異なるアプローチをとっていると主張できます。一方、C ++は同じポインタの場合のCの動作なので、参照は実際にはこの点で奇妙なものとして立っています。


12

それは好みの問題です。

2番目のケースでは、コードを読み取るときに変数とポインターを区別するのが簡単ですが、共通の型の変数とポインターの両方を1行に入れると混乱を招く可能性があります(プロジェクトガイドラインでは、これ自体が推奨されていないことが多く、読みやすさが低下するため)。

タイプ名の横に対応する記号を付けてポインタを宣言することを好みます。たとえば、

int* pMyPointer;

3
問題はCに関するものであり、参照はありません。
Antti Haapala 2016年

指摘していただきありがとうございます。質問はポインターや参照ではなく、基本的にコードのフォーマットに関するものでした。
macbirdie 2017

8

偉大な達人はかつて「コンパイラのように読んでください、あなたはそうしなければなりません」と言っていました。

http://www.drdobbs.com/conversationsa-midsummer-nights-madness/184403835

これはconst配置のトピックに関するものでしたが、ここでも同じルールが適用されます。

コンパイラはそれを次のように読み取ります。

int (*a);

ではない:

(int*) a;

変数の横に星を付ける習慣をつけると、宣言が読みやすくなります。また、次のような目障りを回避します。

int* a[10];

-編集-

として解析されたと私が言っていることを正確に説明すると、それは、の優先順位が高いために、式ではがよりもより強く結合するのと同じように、よりもより強く結合int (*a)することを意味します。*aint4 + 3 * 7 374*

asciiアートの謝罪により、構文解析のためのASTの概要は、int *aおおよそ次のようになります。

      Declaration
      /         \
     /           \
Declaration-      Init-
Secifiers       Declarator-
    |             List
    |               |
    |              ...
  "int"             |
                Declarator
                /       \
               /        ...
           Pointer        \
              |        Identifier
              |            |
             "*"           |
                          "a"

明確に示されているように、共通の祖先がであるため、*はより緊密にバインドしますaDeclarator、をDeclaration含む共通の祖先を見つけるには、ツリーまで上に移動する必要がありますint


いいえ、コンパイラは型をとして確実に読み取ります(int*) a
ランディン

@Lundinこれは、ほとんどの最近のC ++プログラマーが持っている大きな誤解です。変数宣言の解析は次のようになります。ステップ1.宣言の「基本タイプ」になる最初のトークンを読み取ります。 intこの場合。ステップ2.型の装飾を含む宣言を読みます。 *aこの場合。ステップ3次の文字を読みます。コンマの場合は、それを消費してステップ2に戻ります。セミコロンの場合は停止します。それ以外の場合は構文エラーをスローします。...
dgnuff

@Lundin ...パーサーがあなたが提案している方法でそれを読んだ場合、int* a, b;ペアのポインターを作成して取得することができます。私が主張しているの*は、宣言が変数の「基本型」を形成する型ではなく、変数にバインドされて解析されるということです。これは、typedefがtypedef int *iptr; iptr a, b;いくつかのポインタを作成できるように導入された理由の一部でもあります。typedefを使用すると、*をにバインドできますint
dgnuff

1
...確かに、コンパイラーはからの基本型をDeclaration-Specifier「装飾」と「結合」して、Declarator各変数の最終型に到達します。ただし、デコレーション指定子に装飾を「移動」しないint a[10], b;と、完全にばかげた結果が生成され、int *a, b[10];として解析されint *a , b[10] ;ます。それを説明する他の方法はありません。
dgnuff

1
ええ、ここで重要な部分は、実際にはコンパイラの構文解析や構文の順序ではありませんが、コンパイラの型はint*a最終的には「a型があるint*」としてコンパイラに読み込まれます。それが私の元のコメントで私が言ったことでした。
Lundin

3

次のような宣言がある方がより意味があるからです。

int *a, *b;

5
これは文字通り質問を懇願することの例です。いいえ、そのようには意味がありません。「int * a、b」は、両方をポインタにすることもできます。
MichaelGG 2015

1
@MichaelGG犬が尻尾を振っているところがあります。確かにK&R 、へのポインタをint* a, b;作成bすることを指定できたはずintです。しかし、そうではありませんでした。そして正当な理由があります。提案されたシステムでbは、次の宣言のタイプは何int* a[10], b;ですか。
dgnuff

2

1行で複数のポインターを宣言するint* a, * b;ために、整数へのポインターとしてより直感的に "a"を宣言し、同様に "b"を宣言するときにスタイルを混在させないことを好みます。誰かが言ったように、私はとにかく同じ声明で2つの異なる型を宣言しないでしょう。


2

好む人 int* x;タイプを左側に、識別子(名前)を右側にした架空の世界にコードを強制しようとするます。

私は「架空」と言います。

CおよびC ++では、一般的なケースでは、宣言された識別子は型情報で囲まれます。

クレイジーに聞こえるかもしれませんが、あなたはそれが本当であることを知っています。ここではいくつかの例を示します。

  • int main(int argc, char *argv[])mainは、intとへのポインタの配列を取り、charを返す関数であることを意味しますint。つまり、タイプ情報のほとんどは右側にあります。一部の人々は、関数宣言は何らかの形で「特別」であるため、カウントされないと考えています。では、変数を試してみましょう。

  • void (*fn)(int)手段はfn取る関数へのポインタであるintと何も返しません。

  • int a[10]「a」を10 int秒の配列として宣言します。

  • pixel bitmap[height][width]

  • 明らかに、私は私の権利を主張するために右側に多くのタイプ情報を持っている例を厳選しました。のように、型のほとんど(すべてではないにしても)が左側にある宣言はたくさんありますstruct { int x; int y; } center

この宣言構文は、宣言に使用法を反映させるというK&Rの要望から生まれました。単純な宣言を読むことは直感的であり、より複雑な宣言を読むことは、右から左への規則を学習することによって習得できます(スパイラル規則または右左規則だけを呼び出すこともあります)。

Cは非常に単純なので、多くのCプログラマーがこのスタイルを採用し、単純な宣言をとして記述しint *pます。

C ++では、構文が少し複雑になり(クラス、参照、テンプレート、列挙型クラスを使用)、その複雑さへの反応として、多くの宣言で型を識別子から分離するためのより多くの努力が見られます。言い換えると、int* pC ++コードの大規模なスワスをチェックアウトすると、-style宣言がさらに表示される可能性があります。

どちらの言語でも、(1)同じステートメントで複数の変数を宣言しないこと、および(2)s(または、皮肉にもエイリアスを置くエイリアス宣言)を使用することにより、常に変数宣言の左側に型を持つことができます。タイプの左側の識別子)。例えば:typedef

typedef int array_of_10_ints[10];
array_of_10_ints a;

小さな質問、なぜvoid(* fn)(int)は「fnはintを受け入れ、(void *)を返す関数である」という意味ですか?
Soham Dongargaonkar

1
@ a3y3:の括弧は、戻り値の型(*fn)fnはなく、関連付けられたポインタを保持するためです。
エイドリアンマッカーシー、

とった。回答に追加することが重要だと思います。すぐに編集することをお勧めします。
Soham Dongargaonkar、

1
あまり付加価値を付けずに答えを散らかしていると思います。これは単に言語の構文です。構文がこのようになっている理由を尋ねるほとんどの人は、おそらくすでにルールを知っています。この点で混乱している他の人は、これらのコメントで説明を見ることができます。
エイドリアンマッカーシー、

1

私はすでにCPで同様の質問に回答しましたが、誰もそれを言及しなかったので、ここでもCはフリーフォーマット言語あることを指摘する必要があります。このCの特殊性は、C難読化コンテストと呼ばれる非常に特殊なタイプのコンテストにつながります。


1

このトピックの議論の多くは明白な主観的であり、「星は変数名に結びついている」という議論は素朴です。以下は、意見だけではないいくつかの議論です。


忘れられたポインタ型修飾子

正式には、「スター」は型にも変数名にも属していません。ポインタという名前の独自の文法項目の一部です。正式なC構文(ISO 9899:2018)は次のとおりです。

(6.7)宣言:
宣言指定子 init-declarator-list opt ;

ここで、declaration-specifiersにはタイプ(およびストレージ)が含まれ、init-declarator-listにはポインターと変数名が含まれます。この宣言リストの構文をさらに詳しく分析すると、次のようになります。

(6.7.6)declarator:
pointer opt direct-declarator
...
(6.7.6)pointer:
* type-qualifier-list opt
* type-qualifier-list opt pointer

宣言子が宣言全体である場合、直接宣言子は識別子(変数名)であり、ポインターは星形で、その後にポインター自体に属するオプションの型修飾子リストが続きます。

「スターは変数に属している」についてのさまざまなスタイルの引数に一貫性がないのは、これらのポインター型修飾子について忘れていたことです。int* const xint *const xまたはint*const x

考えてint *const a, b;、のタイプaとはb何ですか?「星は変数に属している」ことはもはや明白ではありません。むしろ、人はどこを熟考し始めますconst属しているます。

スターはポインター型修飾子に属しているが、それ以上ではないことを間違いなく主張できます。

ポインタの型修飾子リストは、int *aスタイルを使用する人に問題を引き起こす可能性があります。内部でポインターを使用しtypedef(非常に悪い習慣です!)、「スターは変数名に属している」と考える人は、次の非常に微妙なバグを書く傾向があります。

    /*** bad code, don't do this ***/
    typedef int *bad_idea_t; 
    ...
    void func (const bad_idea_t *foo);

これはきれいにコンパイルされます。ここで、コードがconstで正しいと考えるかもしれません。そうじゃない!このコードは誤って偽のconstの正しさです。

のタイプfooは実際にはint*const*-最も外側のポインターは読み取り専用にされており、データを指していません。この関数の中で、**foo = n;、呼び出し元の変数値を変更します。

これは、式const bad_idea_t *foo*が変数名に属していないためです!疑似コードでは、このパラメーター宣言はとしてconst (bad_idea_t *) fooはなくとして読み取られ(const bad_idea_t) *fooます。この場合、スターは非表示のポインター型に属しています。型はポインターであり、const修飾ポインターは次のように記述されます。*constます。

しかし、上記の例の問題の根本はtypedef*スタイルではなくaの後ろにポインタを隠すことです。


1行での複数の変数の宣言について

1行で複数の変数を宣言することは、悪い習慣として広く認識されています1)。CERT-Cは、次のようにうまくまとめています。

DCL04-C。1つの宣言で複数の変数を宣言しないでください

英語を読むだけで、常識で宣言は1つの宣言であることに同意します。

そして、変数がポインタであるかどうかは問題ではありません。各変数を1行で宣言すると、ほとんどすべての場合でコードが明確になります。

したがって、プログラマーが混乱することについての議論int* a, bは悪いことです。問題の根本は、の配置ではなく、複数の宣言子の使用です*。スタイルに関係なく、代わりにこれを書く必要があります:

    int* a; // or int *a
    int b;

別の健全だが主観的な議論はint* a、型がa疑わしいint*ので、スターは型修飾子に属しているということです。

しかし、基本的に私の結論は、ここに投稿された議論の多くは主観的で素朴なものでしかないということです。どちらのスタイルについても、有効な議論をすることはできません。それは、主観的な個人の好みの問題です。


1) CERT-C DCL04-C


むしろ面白いことに、私がリンクしたその記事を読むとtypedef int *bad_idea_t; void func(const bad_idea_t bar); 、偉大な預言者ダンサックスが「const意味的に意味を変えずに、常にできるだけ右側に配置する場合」のトピックに関する短いセクションがあります。これは完全に停止します。問題になる。また、const宣言を読みやすくすることができます。「constという単語の右側にあるものはすべてconstであり、左側にあるものはすべてその型です。」これは、宣言内のすべてのconstに適用されます。試してみるint const * * const x;
dgnuff

-1

検討する

int *x = new int();

これは変換されません

int *x;
*x = new int();

それは実際に

int *x;
x = new int();

これにより、int *x表記に多少の一貫性がなくなります。


newC ++演算子です。彼の質問には「C ++」ではなく「C」というタグが付いています。
Sapphire_Brick
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.