2016-06-23 36 views
3
int main(int argc, char **argv) { 
    unsigned int ptr1 = *((unsigned int *)(argv[1])); 
    printf("ptr1 = 0x%x\n", ptr1); 
    exit(0); 
} 

これはビデオチュートリアルのコードスニペットです。私はなぜコードが実行時にセグメンテーションフォールトにならないのか分かりません。このCコードスニペットでセグメンテーションフォルトが発生しないのはなぜですか?

(unsigned int *)(argv[1])これは、プログラムに渡される最初の引数がunsigned intにキャストされるようです。したがって、引数が 'AAAA'の場合、 '0x41414141'はメモリ内のいくつかの場所へのポインタになりました。

これを行うと、*((unsigned int *)(argv[1]))は、アドレス0x41414141が指す値を参照していませんか?私が知っていることから、このアドレスはおそらくプロセスによってアクセス可能ではありません。だからどのようにセグメンテーションフォルトが発生しませんか?プログラムの出力は -

ptr1 = 0x41414141

私はgccでLinux上でこのプログラムをコンパイルしています。

+3

'argv [1]'は 'char'データへのポインタを保持しています。ポインタは' unsigned int'データへのポインタとして扱うためにキャストします。あなたは '0x41414141'を逆参照していません。あなたは 'argv [0]'を逆参照していますが、 'unsigned int *'という色付きメガネを使っています。 – WhozCraig

答えて

5

注:この回答は、この特定のケースでコードがどのように機能するかを説明しています。より完全な回答については、@ Lundin postを参照してください。


(unsigned int *)(argv[1])unsigned int*からchar*に変換します。

*((unsigned int *)(argv[1]))逆参照char*からunsigned int*に変換されます。

argv[1]AAAA文字列を指すとします。 41 41 41 41(16進数)としてメモリに格納されます。これをunsigned intと解釈すると、ptr1 = 0x41414141となります。

は、以下の図を見てみましょう:左ビューアはchar*としてメモリを解釈し、右ビューアがunsigned int*として解釈(リトルエンディアン/ビッグエンディアンの違いに気を気にしない):

enter image description here

segfaultの理由はありません

+2

申し訳ありませんが、これは良い答えではありません。コード内のあまり定義されていない動作についてはほとんど言及していません。この回答の読者は、OPのコードは完全に細かいコードであり、上記のチュートリアルは駄目ではないと仮定しているかもしれません。 – Lundin

+1

「だから、セグメンテーションの理由はない」それは間違っている。 'char *'に何かをキャストすることはできますが、逆の方法で行うことで未定義の動作が呼び出されます。 argv [1]は必ずしも正しく整列されていないので、動作することは保証されていません。 x86はアライメントのとれていないアクセスをサポートしているので単純にここで動作しますが、他のほとんどのアーキテクチャでは禁止されています。 –

+0

@ Lundin、Agreed。私が答えを取り除くことができるようにOPが受諾ボタンのチェックを外すのを待つ。 – soon

5

このコードはとても危険です。コードがないと、なぜそれが悪い何であるかのステップバイステップの説明:

  • argvは、文字(char*[])へのポインタの配列です。またはもしそうなら、そのような文字へのポインタの配列の最初の項目へのポインタ(char**)。

  • argv[0]実行可能ファイルの名前を指し、argv[1]は、渡された最初の引数へのポインタです。

  • (unsigned int *)(argv[1])は、char*のポインタをintへのポインタunsigned int*にキャストします。これは決して安全な変換であることは保証されません。この新しい整数アドレスがずれている場合

    • 、それは未定義の動作、おそらくプログラムがクラッシュを呼び出しますアクセス:ここでは二つの主要なバグがあります。

    • 変換は(簡単に言えば)厳密なエイリアシング規則の違反では、コンパイラは、内容がchar*によって指し示さが他のランダムなポインタ型を介してアクセスされることはないと仮定することができると述べています。したがって、コンパイラは、argv[i]が指すメモリがプログラムで使用されることはないと想定することがあります。未定義のビヘイビアを呼び出すと、奇妙な最適化が発生することがあります。特定のコンパイラは非標準の拡張機能として、ポインタの変換のための確定的な動作を指定していること、それが整数だったかのように、それは先の尖った-で文字列にアクセスしようと考える

  • 。文字列がたとえば"ABCD"の場合、結果の整数(32ビットと仮定)は0x41424344または0x44434241のいずれかになります。適用されるのは、CPUのエンディアンに依存します。そのようなコードは移植性がありません。ただメモリへのアクセスが

は、もちろんいかなる影響を引き起こすことはありませんargv配列が指します。このメモリを読み取れなかった場合は、argvパラメータを使用することは不可能です。格納方法はOSに依存しますが、プロセスがアクセスできる内部アドレス空間でなければなりません。

したがって、割り当てられたメモリ内でアクセスを維持している限り、プログラムは確実にクラッシュしたり、エラーが発生したりすることはありません。文字列argv[1]がちょうど1文字の長さだった場合、segフォルトが発生している可能性があります。

+1

アドバイス:YouTubeのチュートリアルをすぐに見てください。コメントを残して、それが悪いチュートリアルだと言う。この投稿へのリンクを投稿して、他人に見られないよう警告してください。 – Lundin

+0

実際、著者はコードが厄介であることを認めていますが、文脈は異なります。ビデオには[link](https://youtu.be/jWI_XjHb12g?t=189)があります。 – user1720897

+0

@ user1720897まあ... durr。そのビデオのコードはあなたの質問と同じではありません。彼は結果を 'unsigned int *'に格納していますが、 'unsigned int'に格納しています。まったく別のプログラムです(でも悪いです)。このビデオは、様々な静的な記憶場所にデータをハッキングすることや、通常のガレージ・ハッカーの考え方である:「これがうまくいくようにする」または「今度はうまくいく」と思われる。 – Lundin

関連する問題