3

私は今日Cに関する本を読んでいて、次のことが真実であると述べました。なぜ私がこのプログラムを検証のために作ったのか、私はとても興味がありました。そして、最終的にここに投稿して、私より賢い人が、これら2つのケースが実行時に異なる理由を教えてくれるようにします。

(char *)がリテラルとして作成された文字列を指しているのか、mallocおよび手動入力で作成された文字列を指しているのかに基づいて、実行時に(char *)が処理される方法の違いに関連する質問の詳細。

なぜこのようにメモリによって割り当てられたメモリがより保護されるのですか?また、答えは「バスエラー」の意味を説明していますか?

これは私が書いたプログラムで、ユーザーにクラッシュするかどうかを尋ね、プログラムが正常にコンパイルされることを示しています。そして、私の頭の中では、両方のオプションのコードが概念的に同一であることを強調します。しかし、それが私がここにいる理由です。なぜそうではないのかを理解するためです。

// demonstrate the difference between initializing a (char *) 
// with a literal, vs malloc
// and the mutability of the contents thereafter
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
int main() {
    char cause_crash;
    char *myString;

    printf("Cause crash? "); 
    scanf("%c", &cause_crash);

    if(cause_crash == 'y') {
        myString = "ab";
        printf("%s\n", myString); // ab
        *myString = 'x'; // CRASH!
        printf("%s\n", myString);   
    } else {
        myString = malloc(3 * sizeof(char));
        myString[0] = 'a';
        myString[1] = 'b';
        myString[2] = '\0';
        printf("%s\n", myString); // ab
        *myString = 'x';
        printf("%s\n", myString); // xb     
    }
    return 0;
}

編集:結論

以下にいくつかの良い答えがありますが、ここで私が理解するようになったものを簡潔に要約したいと思います。

基本的な答えはこれのようです:

コンパイラが「文字列リテラル」が(char *)変数に割り当てられていることを確認すると、ポインタは静的なメモリを指します(おそらく実際にはバイナリの一部ですが、通常は、より低いレベルのシステムによって読み取り専用として強制されます。つまり、メモリはプログラムのその部分で動的に割り当てられるのではなく、リテラルの内容を格納する静的メモリの領域を指すようにポインタが設定されるだけです。

この解決策について私が指摘したいことがいくつかあります。

1.最適化が動機となる可能性があります。私のコンパイラでは、同じ文字列リテラルで初期化された2つの異なる(char *)変数が実際には同じアドレスを指しています。

char *myString = "hello";
char *mySecond = "hello"; // the pointers are identical! This is a cool optimization.

2興味深いことに、変数が実際に((char *)ではなく)charの配列である場合、この(#1)は真ではありません。これは私にとって興味深いものでした。なぜなら、(コンパイル後の)配列はcharsへのポインターと同一であるという印象を受けていたからです。

char myArString[] = "hello";
char myArSecond[] = "hello"; // the pointers are NOT the same

3いくつかの答えが示唆したことを要約すると、新しいメモリを割り当てchar *myString = "Hello, World!" 、すでに存在するメモリを指すようにmyStringを設定するだけです。おそらくバイナリで、おそらくメモリの特別な読み取り専用ブロックで...など。

4テストを通じて、新しいメモリを割り当てることchar myString[] = "Hello, World!" わかりました。私が知っているのは、この方法で作成した場合、文字列は変更可能であるということです。

4

5 に答える 5

2

あなたは本当にmyStringとして宣言すべきでしたconst char*。リテラルは読み取り専用メモリに格納されているため、変更できません。char[]変更する必要がある場合は、を使用してください。

于 2012-07-07T23:18:22.150 に答える
2

myString = "ab";

読み取り専用メモリにある定数文字列リテラルのアドレスをcharポインタに割り当てますmyString

ここでこのメモリに書き込むと、クラッシュします。

OTOH、もちろん、malloc()edメモリに楽しく書き込むことができるので、うまくいきます。

于 2012-07-07T23:20:07.610 に答える
1

C標準では、リテラル文字列は静的であり、それらを変更しようとすると未定義の動作が発生することが指定されています。つまり、読み取り専用と見なす必要があります。

割り当てたメモリは自分のmallocものであり、好きなように変更できます。

実際の違いは実装に依存する可能性がありますが、通常、各タイプの文字列は2つの異なるタイプ/メモリ領域にあります。

  • を使用して取得したデータの場合のヒープmalloc、および
  • 文字列リテラルの場合の(読み取り専用)データセクション。
于 2012-07-07T23:22:32.390 に答える
1

変数を文字列リテラルに設定すると、アセンブリプログラムの読み取り専用データセクションに格納されている値に設定されます。これらのデータ項目は一定であり、それらを別の方法で使用しようとすると、クラッシュする可能性が高くなります。

を使用mallocしてメモリを取得すると、何でも実行できるヒープメモリの読み取り/書き込みへのポインタを取得します。

これはいくつかの理由によって引き起こされます。一つには、実際のタイプは"Hello, world"、、char[13]または13文字への定数ポインタです。定数文字に値を割り当てることはできません。しかし、あなたがすることのようなことをするとき、それは恒常性を捨てることです。つまり、コンパイラはメモリの変更を妨げませんが、C標準呼び出しは未定義の動作です。未定義の動作は何でもかまいませんが、通常はクラッシュです。

リテラル値をchar*メモリに割り当てる場合は、次のようにします。

char* data = malloc (42);
memcpy(data, "Hi!", 4);
于 2012-07-07T23:25:41.517 に答える
0

これを書いたらどうなるでしょう:

&mystring = &"ab";

それはあなたにとってどういう意味ですか?

どういうわけか「ab」を変更できると思いますか?&"ab"はどこにありますか?

ANS:&"ab"は読み取り専用メモリにあります。コンパイラがそのQUOTEを確認すると、その文字列を不変のメモリに入れます。なんで?ランタイムが境界チェックやセグメンテーション違反などをチェックする必要がない場合は、おそらく何とか高速です。本当に変更されるべきではない文字列データ。

于 2012-07-07T23:34:12.553 に答える