コンピューティング において、初期化されていない変数とは、宣言されているものの、使用される前に明確な既知の値に設定されていない変数のことです。何らかの値を持ちますが、予測可能な値ではありません。そのため、これはプログラミング エラーであり、ソフトウェアのバグの一般的な原因です。
C言語の例
初心者プログラマーがよく思い込むのは、すべての変数は宣言時に 0 などの既知の値に設定されるというものです。これは多くの言語に当てはまりますが、すべての言語に当てはまるわけではないため、エラーが発生する可能性があります。C などの言語は変数にスタック領域を使用し、サブルーチンに割り当てられた変数のコレクションはスタック フレームと呼ばれます。コンピューターはスタック フレーム用に適切な量の領域を確保しますが、通常はスタックポインターの値を調整するだけでこれを実行し、メモリ自体を新しい状態に設定することはありません (通常は効率上の懸念から)。したがって、その時点でのメモリの内容が、そのアドレスを占める変数の初期値として表示されます。
以下は C での簡単な例です。
void count ( void ) { int k , i ; for ( i = 0 ; i < 10 ; i ++ ) { k = k + 1 ; } printf ( "%d" , k ); }
の最終値はk未定義です。10 でなければならないという答えは、ゼロから始まったと仮定していますが、これは正しい場合もそうでない場合もあります。例では、変数はステートメントiの最初の節によってゼロに初期化されることに注意してくださいfor。
もう 1 つの例として、構造体を扱う場合が挙げられます。以下のコード スニペットには、struct student学生に関する情報を記述する変数がいくつか含まれています。関数 は、register_studentのメンバーを完全に初期化できないため、メモリ コンテンツをリークしますstruct student new_student。詳しく見てみると、最初に、、ageおよびsemesterが初期化されています。しかし、およびメンバーstudent_numberの初期化が正しくありません。これは、および文字配列の長さが 16 バイト未満の場合、 中に、[1]これらのメンバーごとに予約されている 16 バイトのメモリ全体を完全に初期化できないためです。したがって、結果の構造体を に ' した後、[2]呼び出し元にスタック メモリの一部がリークします。
first_namelast_namefirst_namelast_namestrcpymemcpy()output
構造体 学生{ unsigned int年齢; unsigned int学期; char first_name [ 16 ]; char last_name [ 16 ]; unsigned int学生番号; };
int register_student ( struct student * output 、int age 、char * first_name 、char * last_name ) { // これらのポインタのいずれかが Null の場合、失敗します。if ( ! output || ! first_name || ! last_name ) { printf ( "Error! \n " ); return -1 ; }
//オーバーフローを回避するために、文字列の長さが 16 バイト未満 (ヌルバイトを含む) であることを確認します。if ( strlen ( first_name ) > 15 || strlen ( last_name ) > 15 ) { printf ( "first_name と last_name は 16 文字より長くすることはできません。\n " ); return -1 ; }
//メンバーを初期化しますstruct student new_student ; new_student.age = age ; new_student.semester = 1 ; new_student.student_number = get_new_student_number ( ) ; strcpy ( new_student.first_name , first_name ) ; strcpy ( new_student.last_name , last_name ) ;
//結果を出力にコピーします
memcpy ( output , & new_student , sizeof ( struct student )); return 0 ; }
いずれにしても、変数が0 などのデフォルト値に暗黙的に初期化されている場合でも、これは通常正しい値ではありません。値がデフォルト値である場合、初期化されているからといって正しいとは限りません。(ただし、ポインターとポインターの配列については、正しい値に実際に初期化される前に無効にするため、デフォルトで0に初期化することは正しい方法です。) C では、明示的に初期化されていない静的記憶域期間を持つ変数は、0 (ポインターの場合は null) に初期化されます。[3]
初期化されていない変数はバグの原因となることがよくあるだけでなく、この種のバグは再現できない場合があるため特に深刻です。たとえば、変数がプログラムの一部のブランチでのみ初期化されていないままになることがあります。場合によっては、初期化されていない変数を持つプログラムがソフトウェア テストに合格することさえあります。
影響
初期化されていない変数は、状況に応じて、任意のメモリをリークしたり、任意のメモリ上書きを達成したり、コード実行を獲得したりするために悪用される可能性があるため、強力なバグです。アドレス空間レイアウトのランダム化(ASLR) を利用するソフトウェアを悪用する場合、メモリ内のソフトウェアのベース アドレスを知る必要があることがよくあります。初期化されていない変数を悪用してソフトウェアにアドレス空間からポインターをリークさせると、ASLR をバイパスできます。
言語での使用
初期化されていない変数は、システム プログラミング用に設計されたアセンブリ言語、C、C++などの言語で特に問題になります。これらの言語の開発には、パフォーマンスと安全性の矛盾がパフォーマンスを優先して解決されるという設計哲学が伴いました。プログラマーには、初期化されていない変数などの危険な問題を認識する責任が課せられました。
他の言語では、変数は作成時に既知の値に初期化されることがよくあります。例:
- VHDL は、すべての標準変数を特別な「U」値に初期化します。これは、シミュレーションやデバッグで使用され、マルチ値ロジックを通じて、ドントケア初期値が出力に影響を与えるタイミングをユーザーに知らせます。
- Javaには初期化されていない変数はありません。明示的な初期化子を持たないクラスやオブジェクトのフィールド、および配列の要素は、その型のデフォルト値(ブール型の場合はfalse、すべての数値型の場合は0、すべての参照型の場合はnull)で自動的に初期化されます。[4] Javaのローカル変数は、アクセスする前に確実に割り当てる必要があります。そうしないと、コンパイルエラーになります。
- Python は
NULLローカル変数を( とは異なるNone)に初期化し、UnboundLocalErrorそのような変数が有効な値に (再) 初期化される前にアクセスされると を発生させます。 - D は、プログラマーによって明示的に指定されない限り、すべての変数を初期化します。
初期化されていない変数が許可されている言語でも、多くのコンパイラは初期化されていない変数の使用を識別し、コンパイル時 エラーとして報告しようとします。一部の言語では、変数の初期化を処理するための構造を提供することで、このタスクを支援します。たとえば、C#には、サブルーチンへの参照呼び出しパラメータの特別なフレーバーがあり (out通常の ではなく として指定されますref)、変数はエントリ時に初期化されていないことが許可されているが、後で初期化されることをアサートします。
参照
参考文献
- ^ strcpy
- ^ memcpy()
- ^ 「ISO/IEC 9899:TC3 (現在のC規格)」(PDF) 2007年9月7日 p. 126 。 2008年9月26日閲覧。セクション6.7.8、パラグラフ10。
- ^ 「Java 言語仕様: 4.12.5 変数の初期値」。Sun Microsystems。2008年 10 月 18 日閲覧。
さらに読む
- 「CWE-457 初期化されていない変数の使用」。
