具体的には、mallocの結果をキャストすることの危険性は何ですか?


86

人々がこれを重複とマークし始める前に、私は以下のすべてを読みましたが、どれも私が探している答えを提供していません:

  1. C FAQ:mallocの戻り値をキャストすることの何が問題になっていますか?
  2. SO:malloc()の戻り値を明示的にキャストする必要がありますか?
  3. SO:Cでの不要なポインターキャスト
  4. SO:mallocの結果をキャストしますか?

C FAQと上記の質問に対する多くの回答の両方が、キャストmallocの戻り値が隠すことができるという不思議なエラーを引用しています。ただし、実際にはそのようなエラーの具体例を示すものはありません。今、私がエラーを言ったことに注意してください、ではなく警告

ここで、次のコードが与えられます。

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

int main(int argc, char** argv) {

    char * p = /*(char*)*/malloc(10);
    strcpy(p, "hello");
    printf("%s\n", p);

    return 0;
}

上記のコードをgcc4.2でコンパイルすると、キャストがある場合とない場合で同じ警告が表示され、プログラムは正しく実行され、どちらの場合も同じ結果が得られます。

anon@anon:~/$ gcc -Wextra nostdlib_malloc.c -o nostdlib_malloc
nostdlib_malloc.c: In function ‘main’:
nostdlib_malloc.c:7: warning: incompatible implicit declaration of built-in function ‘malloc’
anon@anon:~/$ ./nostdlib_malloc 
hello

では、キャストmallocの戻り値が原因で発生する可能性のあるコンパイルエラーまたはランタイムエラーの特定のコード例を誰かに示すことができますか、それともこれは単なる都市伝説ですか?

編集私はこの問題に関して2つのよく書かれた議論に出くわしました:

  1. キャスティングに賛成:CERTアドバイザリ: キャストをメモリ割り当て関数呼び出しの結果を、割り当てられたタイプへのポインタにすぐにキャストします
  2. キャストに対して (2012-02-14現在の404エラー:2010-01-27からのインターネットアーカイブウェイバックマシンのコピーを使用してください。{2016-03-18: "robots.txtのため、ページをクロールまたは表示できません。"})

6
voidポインタをキャストすると、コードをC ++としてコンパイルできます。これは機能だと言う人もいますが、バグだと思います;)
Christoph

1
また、キャストする代わりに何をすべきかを説明しているリンクの最初のコメントを読んでください:securecoding.cert.org/confluence/display/seccode/…–
Christoph

3
キャストを含めるためにCERTのアドバイスを受けます。また、stdlib.hを含めることを忘れません。:)
Abhinav 2012年

1
これは、キャストmallocの戻り値が原因でコンパイルランタイムエラーが発生したSOの例です:int*64ビットアーチでのキャスト。
John_West 2015

1
この質問にはタグが付けられてCいませんC++(2つの異なる言語です)。したがって、(一部の回答のように)どのディスカッションもこの質問には関係ありません。
user3629249 2017年

回答:


66

コンパイラエラーは発生しませんが、コンパイラ警告発生します。あなたが引用している情報源(特に最初のものが言うように、キャストを含めずにキャスト使用すると、予測できないランタイムエラーが発生する可能性がありますstdlib.h

したがって、あなたの側のエラーはキャストではなく、を含めるのを忘れていますstdlib.h。コンパイラは、それがをmalloc返す関数であると想定する場合がありますint。したがって、明示的なキャストにより、void*実際に返されmallocintポインタをポインタ型に変換し、次にポインタ型に変換します。一部のプラットフォームではint、ポインタが異なるバイト数を使用する可能性があるため、型変換によってデータが破損する可能性があります。

幸い、最近のコンパイラは、実際のエラーを示す警告を出します。gcc提供した出力を参照してください。暗黙の宣言(int malloc(int))が組み込みのと互換性がないことを警告しますmalloc。だからなくgccmallocも知っているようですstdlib.hです。

このエラーを防ぐためにキャストを省くことは、書くこととほとんど同じ理由です。

if (0 == my_var)

の代わりに

if (my_var == 0)

後者はとを混乱させる=と深刻なバグにつながる可能性があるのに対し、前者は==コンパイルエラーにつながる可能性があるためです。私は後者のスタイルを個人的に好みます。なぜなら、それは私の意図をよりよく反映していて、この間違いをする傾向がないからです。

によって返される値をキャストする場合も同じです。mallocプログラミングで明示的にすることを好み、通常、使用するすべての関数のヘッダーファイルを含めるように再確認します。


2
コンパイラーは互換性のない暗黙の宣言について警告するので、コンパイラーの警告に注意を払う限り、これは問題ではないように思われます。
ロバートS.バーンズ

4
@Robert:はい、コンパイラに関する特定の仮定があります。一般的にCの書き方についてアドバイスをしているとき、アドバイスを受けている人が最新バージョンのgccを使用しているとは限りません。
Steve Jessop

4
ああ、2番目の質問に対する答えは、呼び出し元に戻り値(intと見なされる)を取得してT *に変換するためのコードが含まれているということです。呼び出し先は、戻り値を(void *として)書き込み、戻ります。したがって、呼び出し規約に応じて、intリターンとvoid *リターンは、「同じ場所」(レジスタまたはスタックスロット)にある場合とない場合があります。intとvoid *は、同じサイズである場合とそうでない場合があります。2つの間での変換は、何もしない場合とそうでない場合があります。そのため、「正常に機能する」か、値が破損するか(ビットが失われる可能性があります)、呼び出し元が完全に間違った値を取得する可能性があります。
Steve Jessop

1
@ RobertS.Barnesはパーティーに遅れましたが、C ++であっても、戻り値は通常、関数シグネチャの一部ではありません。リンカはシンボルへのジャンプを生成するだけです。それだけです。
ピーター-モニカを復活させる2015年

3
stdlib.hを含めずにキャストを使用すると、予測できないランタイムエラーが発生する可能性があります。それは本当ですが、stdlib.h「暗黙の宣言」の警告しか表示されない場合でも、含まないこと自体はすでにエラーです。
Jabberwocky 2016

45

malloc私の意見では、よく知られている低レベルの問題(宣言が欠落しているときにポインタを切り捨てるなど)よりも重要ですが、結果をキャストすることに対する優れた高レベルの議論の1つはしばしば言及されません。

プログラミングの良い習慣は、可能な限り型に依存しないコードを書くことです。これは、特に、型名をコード内でできるだけ言及しないか、まったく言及しないことをお勧めします。これは、キャスト(不要なキャストを避ける)、引数としてのsizeof型(で型名の使用を避けるsizeof)、および一般に、型名への他のすべての参照に適用されます。

タイプ名は宣言に属します。可能な限り、型名は宣言に限定し、宣言のみに限定する必要があります。

この観点から、このコードのビットは悪いです

int *p;
...
p = (int*) malloc(n * sizeof(int));

そしてこれははるかに優れています

int *p;
...
p = malloc(n * sizeof *p);

「の結果をキャストしない」という理由だけでmallocなく、タイプに依存しない(または、必要に応じてタイプに依存しない)ためp、宣言されたタイプに自動的に調整され、からの介入は必要ありません。ユーザー。


Fwiw、これは多かれ少なかれこれと同じ理由だと思います:stackoverflow.com/questions/953112/…しかし、DIYではなくタイプに依存しないことに焦点を当てています。もちろん、最初のものは2番目のものから続く(またはその逆)ので、少なくとも時々言及されます。:)
くつろぐ

5
@unwindあなたはおそらくDIYではなくDRYを意味します
kratenko 2012年

18

プロトタイプ化されていない関数は、を返すと想定されintます。

つまりint、をポインタにキャストしているのです。intプラットフォーム上のポインタの幅がsよりも広い場合、これは非常に危険な動作です。

さらに、当然のことながら、一部の人々は警告があると考えていること、エラー、すなわちコードはそれらなしでコンパイルする必要があります。

個人的には、void *別のポインター型にキャストする必要がないという事実はCの機能だと思います。そして、壊れているコードを検討してください。


14
私は、コンパイラーが私よりも言語についてよく知っていると信じているので、何かについて警告が出た場合は注意を払います。
ジェルジAndrasek

3
多くのプロジェクトでは、CコードはC ++としてコンパイルされ、キャストする必要がありますvoid*
laalto 2009年

nit: "デフォルトでは、プロトタイプ化されていない関数はint。を返すと見なされます。" -プロトタイプ化されていない関数の戻り値の型を変更できるということですか?
pmg 2009年

1
@ laalto-そうですが、そうすべきではありません。CはC ++ではなくCであり、C ++コンパイラではなくCコンパイラでコンパイルする必要があります。言い訳はありません。GCC(そこにある最高のCコンパイラの1つ)は、考えられるほぼすべてのプラットフォームで実行されます(そして、高度に最適化されたコードも生成します)。怠惰と緩い標準以外に、CをC ++コンパイラでコンパイルしなければならない理由は何ですか?
クリス・ルッツ

3
CとC ++の両方としてコンパイルしたいコードの例:#ifdef __cplusplus \nextern "C" { \n#endif static inline uint16_t swb(uint16_t a) {return ((a << 8) | ((a >> 8) & 0xFF); } \n#ifdef __cplusplus\n } \n#endif。さて、なぜ静的インライン関数でmallocを呼び出したいのか、私にはよくわかりませんが、両方で機能するヘッダーはほとんど前例のないものではありません。
Steve Jessop

11

64ビットモードでコンパイルするときにこれを行うと、返されるポインタは32ビットに切り捨てられます。

編集:短すぎて申し訳ありません。これは、議論のためのサンプルコードフラグメントです。

メイン()
{{
   char * c =(char *)malloc(2);
   printf( "%p"、c);
}

返されたヒープポインタが、intで表現できるものよりも大きいもの、たとえば0xAB00000000であるとします。

mallocがポインタを返すようにプロトタイプ化されていない場合、返されるint値は、最初はすべての有効ビットが設定されたレジスタにあります。これで、コンパイラは「わかりました。ポインタに変換して整数化するにはどうすればよいですか」と言います。これは、プロトタイプを省略することでmallocが「戻る」と言われている下位32ビットの符号拡張またはゼロ拡張のいずれかになります。intは符号付きなので、変換は符号拡張になると思います。この場合、値はゼロに変換されます。戻り値が0xABF0000000の場合、ゼロ以外のポインターを取得します。これは、逆参照しようとしたときにも楽しい結果になります。


1
これがどのように発生するかを詳しく説明していただけますか?
ロバートS.バーンズ

5
私はPeeter JootはSTDLIB.Hを含むO / W「デフォルトで、非プロトタイプ関数は戻りintに想定される」ことを考え出すたと思う、およびsizeof(PTR)が64である間はsizeof(INT)は32ビットである
テスト

4

再利用可能なソフトウェアルール:

malloc()を使用するインライン関数を作成する場合、C ++コードでも再利用できるようにするために、明示的な型キャスト((char *)など)を実行してください。そうしないと、コンパイラは文句を言います。


うまくいけば、gccにリンク時の最適化が(最近)含まれているため(gcc.gnu.org/ml/gcc/2009-10/msg00060.htmlを参照)、ヘッダーファイルでインライン関数を宣言する必要がなくなります
Christoph

あなたは悪い考えを持っています。異なるコンパイラ/バージョン/アーキテクチャ間で移植可能でクロスプラットフォームなものがあることをご存知ですか?わかりました、あなたはそうしないかもしれません。では、再利用可能とはどういう意味ですか?
テスト

2
C ++を作成する場合、malloc / freeは適切な方法ではありません。むしろnew / deleteを使用してください。IEは、C ++コードでmalloc / freeへのno / nada / zero呼び出しがないはずです
user3629249 2015年

3
@ user3629249:内から使用できる必要がある機能書き込むときのいずれかのCコードまたはC ++コードを使用して、malloc/free両方のためには、より有効に活用しようとよりなりやすいmallocCにし、newデータ構造はCとCの間で共有されている場合は特に、C ++で++コードであり、オブジェクトがCコードで作成されてC ++コードでリリースされる、またはその逆の可能性があります。
スーパーキャット2017年

3

Cのvoidポインターは、明示的なキャストなしで任意のポインターに割り当てることができます。コンパイラは警告を出しますが、対応する型に型キャストすることでC ++再利用できますmalloc()Cは厳密な型チェックではないため、型キャストなしでもCで使用できます。ただし、C ++は厳密に型チェックであるため、C ++で型キャストする必要があります。malloc()


C ++でmallocを使用する場合は、正当な理由があります。; p
2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.