2016-10-15 4 views
3

私はC++で経験が不十分です。文字と文字列がどのように動作するかを見るために次のコードを書いています。C++のchar配列の予期しない出力

#include "stdio.h" 
#include <iostream> 
#include <string> 

using namespace std; 

int main() 
{ 
    char asdf[] = "hello"; 
    char test[5] = {'h','e','l','l','o'}; 
    cout << test; 
} 

"hello"を出力すると予想されましたが、代わりに "hellohello"が得られました。これは本当に困惑しています。私はいくつかの実験を行った:

別の長さの別の文字列にasdfを変更すると、正常に "hello"が出力されます。 テストの文字数を変更すると、正常に "hello"が出力されます。

私はこれが2つの同じ長さだったときに起こったと思ったが、私はそれらを両方とも「地獄」に変更すると、正常に「地獄」を出力するようだ。

私は友人に自分のコンピュータでこのコードを実行するように頼んだら、もっと混乱させるために、「hello」とランダムな文字を出力しました。

私はUbuntuで新しいコードブロックをインストールしています。誰でもここで何が起こっているか考えている?

+0

を今後の研究のために有用であろう、より良いタイトルを書いてみてください。 –

+0

確かに、どのようなタイトルをお勧めしますか?問題を簡潔に説明する方法は考えられませんでした。 –

+0

_ "予期しない文字配列の出力" _のようなものがあります。とにかく状況を説明するものは、より明確に会って、他の人が調査することを可能にします。 –

答えて

8

これは未定義の動作です。

Raw char*またはchar[] CおよびC++の文字列は、NULL-terminatedである必要があります。つまり、文字列は'\0'文字で終わる必要があります。 test[5]はこれを実行しないので、最後のoの後に出力を出力する関数は引き続きNULL終了を探しているので、この関数は出力を続けます。どのように文字列がどのあなたは"hello"を割り当てるために、それが発生した次のバイトはasdf[]のものであり、(スタックは通常アドレスに向けて成長する)stackに保存されている。へ

これは、矢印はメモリアドレス(思考ポインタ)が増加する方向を示し、同様にメモリレイアウトは、実際にどのように見えるかです:

 ----> 
    +------------------- 
    |hellohello\0 ... 
    +------------------- 
     \_ asdf 
    \_ test 

は今C++とCで、"hello"のような文字列リテラルは、NULLで終了する暗黙的なので、コンパイラは文字列の末尾に隠された'\0'を書き込みます。出力関数は、asdf char-by-charの内容が隠された'\0'に達するまでそれを印刷して停止します。

asdfを削除すると、最初のhelloの後に少しのゴミが表示され、セグメント化エラーが発生する可能性があります。しかし、test配列の境界から読み取っているため、これは未定義の動作です。これはまた、異なるシステムで異なる動作をする理由を説明します。例えば、コンパイラの中にはスタック上の異なる順序で変数をレイアウトすることを決める場合があります。そのため、友人システムではtestがスタック上で実際に低くなりますスタック)が上位アドレスに位置意味:あなたはtestの内容を印刷するとき

 ----> 
    +------------------- 
    |hello\0hello ... 
    +------------------- 
      \_ test 
    \_ asdf 

は今、それが\0が発見されるまで、メモリを読み続け、その後、hello CHARバイ文字が印刷されます。 ...の内容は、アーキテクチャや実行時に非常に特有のものであり、おそらく月や時刻のフェーズ(まったく深刻ではない)であっても、友人のマシンでは「ランダム」な文字を印刷して停止します。

testアレイに'\0'または0を追加することで問題を解決できます(サイズを6に変更する必要があります)。しかし、これを解決するにはconst char test[] = "hello";を使用するのが一番良い方法です。

+0

答えは良いですが、私はあなたがノートで取得しようとしているか分からない。 – juanchopanza

+0

説明をいただきありがとうございます! asdf行を削除すると正常に表示されるようです。これはヌルターミネーターがないという事実を修正するためのC++の魔法ですか? –

+0

@KieronDowie私の編集を確認してください。私はいくつかのASCIIアートグラフィックスを追加して、メモリのレイアウト方法を示しました。 '' asdf''を削除すると、2番目のグラフィックに似ています。 '' ... ''の内容は簡単にNUL文字で始まり、出力を終了することがあります。 –

5

test配列をascii 0 charで終了する必要があります。現在、メモリ内にasdf文字列に隣接しているので、testは終了していないので、<<は、asdfの末尾にあるascii 0を満たすまでそのまま続きます。

あなたが不思議の場合:asdfを記入すると、このアスキー0が自動的に追加されます。

4

この理由は、Cスタイルの文字列では文字列の末尾にヌル文字が必要であるためです。

これを配列testに入れていないので、文字が見つかるまで印刷していきます。あなたの場合、配列asdfはメモリ内でtestに従うことになりますが、これは保証されません。

代わりに、これにコードを変更:

char test[] = {'h','e','l','l','o', 0}; 
+0

うわー、速い答え。ありがとう、それは問題と思われる! –

+0

Typo:は 'char test [6]'でなければなりません。 – juanchopanza

+0

ありがとうございます - 'charテスト[]'する必要があります –

2

coutは、ヌルターミネータを検出するまで、指定されたアドレスの先頭(testここ、または&test[0]相当の表記)からすべての文字を印刷しています。ヌルターミネータをテスト配列に配置していないので、誤ってメモリ内にターミネータが見つかるまで、ターミネータは印刷され続けます。この時点から、何が起こるかは未定義の動作です。

0

最後の文字は、文字列の最後を示すために'\0'である必要があります。

char test[6] = {'h','e','l','l','o','\0'}; 
0

5つの文字、CHARおよびオペレータによってCスタイルの文字列として処理するポインタのアレイであろう「崩壊」の配列を参照するためのoperator<<の過負荷がある場合を除きます。 Cスタイルの文字列は、配列には欠けている0文字で終了します。したがって、演算子はメモリ内のバイトを出力し続け、印刷可能な文字として解釈します。スタック上で2つの配列が隣接しているので、オペレータはasdfのメモリ領域に入り、それらの文字を出力し、最終的に"hello"の最後にある暗黙の0の文字に遭遇しました。他の宣言を省略すると、プログラムがクラッシュする可能性があります。つまり、次の0バイトがプログラムのメモリ境界よりも後に来る場合です。

オブジェクトの外部のメモリ(ここではtest)にそのオブジェクトへのポインタでアクセスすることは未定義の動作です。

0

文字シーケンスには、null terminator\0)が必要です。修正さ

char asdf[] = "hello"; // OK: String literals have '\0' appended at the end 
char test[5] = {'h','e','l','l','o'}; // Oops, not null terminated. UB 

char test[6] = {'h','e','l','l','o','\0'}; // OK 
//  ^      ^^^^