web-dev-qa-db-ja.com

スタックにchar []があるのに、ヒープにchar *があるのはなぜですか?

私は何が起こっているのか非常に混乱しています。いつも思っていたchar *およびchar []は交換可能でしたが、メモリアドレスを調べたところ、char *はヒープにスペースを割り当てますが、char []はスタックにメモリを割り当てています。

char stack[] = "hello";
char *heap = "hello";

char *heap_string_malloc = malloc(5);
heap_string_malloc = "hello";

printf("Address of stack[0]: %p\n", stack);
printf("Address of heap[0]: %p\n", heap);
printf("Address of heap_string_malloc[0]: %p\n", heap_string_malloc);

以下を出力します。

Address of stack[0]: 0x7fff8b0b85b0
Address of heap[0]: 0x400760
Address of heap_string_malloc[0]: 0x400760

これはchar *は動的に割り当てられますか?

さらに混乱して、どうしてmallocは何と同じメモリアドレスを割り当てているのかchar *heapはすでに割り当てられていますか?私は最適化を実行していません(単にgcc file.c)。

23
tgun926

配列はポインタではありません 。プログラムが行ごとに行っていることは

// Allocate 6 bytes in the stack and store "hello" in them
char stack[] = "hello";

// Allocate pointer on the stack and point it to a static, read-only buffer
// containing "hello"
char *heap = "hello";

// Malloc 5 bytes (which isn't enough to hold "hello" due to the NUL byte)
char *heap_string_malloc = malloc(5);

// Reset heap_string_malloc to point to a static buffer; memory leak!
heap_string_malloc = "hello";

同じポインターが2回表示されるのは、コンパイラーが"hello"を含む2番目の静的バッファーを最適化して削除したためです。

36
Fred Foo

あなたが行うとき.

char *heap = "hello";

ポインタheapは実際にはヒープを指さず、オペレーティングシステムローダーによって残りのプログラムと一緒にロードされた静的データを指しています。実際、正しいことは

const char *heap = "hello";

heap定数および読み取り専用のメモリの一部を指しています。


また、配列はポインタに減衰し(ポインタとしても使用できます)、ポインタは配列構文で使用できますが、それらはnotと同じです。最大の違いは、配列で使用できることです。 sizeofは、実際の配列のサイズ(バイト単位)を取得しますが、ポインターは使用できません。


そして第三に、あなたがやっているとき

char *heap_string_malloc = malloc(5);
heap_string_malloc = "hello";

最初に何かをheap_string_mallocに割り当て、その後すぐにheap_string_mallocを再割り当てして、まったく異なるものを指すようにするため、メモリリークが発生します。


heapheap_string_mallocの両方で同じアドレスを取得する理由は、両方が同じリテラル文字列を指しているためです。

11

_"hello"_などの文字列リテラルは、プログラムの存続期間中保持されるように格納されます。多くの場合、読み取り専用の個別のデータセグメント(スタックまたはヒープとは異なる)に格納されます。

あなたが書くとき

_char stack[] = "hello";
_

タイプ「autoの6要素配列」(サイズは文字列リテラルの長さから取得されます)の新しいchar( "スタック")変数を作成し、コンテンツ文字列リテラル_"hello"_がそれにコピーされます。

あなたが書くとき

_char *heap = "hello";
_

タイプ "pointer to auto"の新しいchar( "stack")変数を作成していて、文字列リテラル_"hello"_のaddressがそれにコピーしました。

これが私のシステムでどのように見えるかです:

_       Item        Address   00   01   02   03
       ----        -------   --   --   --   --
    "hello"       0x400b70   68   65   6c   6c    hell
                  0x400b74   6f   00   22   68    o."h

      stack 0x7fffb00c7620   68   65   6c   6c    hell
            0x7fffb00c7624   6f   00   00   00    o...

       heap 0x7fffb00c7618   70   0b   40   00    p.@.
            0x7fffb00c761c   00   00   00   00    ....

      *heap       0x400b70   68   65   6c   6c    hell
                  0x400b74   6f   00   22   68    o."h
_

ご覧のとおり、文字列リテラル_"hello"_には、アドレス0x400b70から始まる独自のストレージがあります。両方のstack ahd heapvariablesは、auto( "スタック")変数として作成されます。 stackには文字列リテラルの内容のcopyが含まれ、heapには文字列リテラルのaddressが含まれます。

ここで、mallocを使用して文字列にメモリを割り当て、結果をheapに割り当てるとします。

_heap = malloc( sizeof *heap * strlen( "hello" + 1 ));
strcpy( heap, "hello" );
_

これで、私のメモリマップは次のようになります。

_       Item        Address   00   01   02   03
       ----        -------   --   --   --   --
    "hello"       0x400b70   68   65   6c   6c    hell
                  0x400b74   6f   00   22   68    o."h

      stack 0x7fffb00c7620   68   65   6c   6c    hell
            0x7fffb00c7624   6f   00   00   00    o...

       heap 0x7fffb00c7618   10   10   50   00    ..P.
            0x7fffb00c761c   00   00   00   00    ....

      *heap       0x501010   68   65   6c   6c    hell
                  0x501014   6f   00   00   00    o...
_

heap変数に異なるアドレスが含まれるようになりました。これはyet another文字列「hello」を含むメモリの6バイトのチャンクを指します。

[〜#〜]編集[〜#〜]

Byteofthatについて、上記のマップを生成するために使用するコードは次のとおりです。

dumper.h:

_#ifndef DUMPER_H
#define DUMPER_H

/**
 * Dumps a memory map to the specified output stream
 *
 * Inputs:
 *
 *   names     - list of item names
 *   addrs     - list of addresses to different items
 *   lengths   - length of each item
 *   count     - number of items being dumped
 *   stream    - output destination
 *
 * Outputs: none
 * Returns: none
 */
void dumper(char **names, void **addrs, size_t *lengths, size_t count, FILE *stream);

#endif
_

dumper.c:

_#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#include "dumper.h"

/**
 * Dumps a memory map to the specified output stream
 *
 * Inputs:
 *
 *   names     - list of item names
 *   addrs     - list of addresses to different items
 *   lengths   - length of each item
 *   count     - number of items being dumped
 *   stream    - output destination
 *
 * Outputs: none
 * Returns: none
 */
void dumper(char **names, void **addrs, size_t *lengths, size_t count, FILE *stream)
{
  size_t i;
  int maxlen = 15;

  for ( size_t j = 0; j < count; j++ )
  {
    if (strlen(names[j]) > maxlen && strlen(names[j]) < 50)
      maxlen = strlen(names[j]);
  }

  fprintf(stream,"%*s%15s%5s%5s%5s%5s\n", maxlen, "Item", "Address", "00", "01",
    "02", "03");
  fprintf(stream,"%*s%15s%5s%5s%5s%5s\n", maxlen, "----", "-------", "--", "--",
    "--", "--");

  for (i = 0; i < count; i++)
  {
    size_t j;
    char *namefield = names[i];
    unsigned char *p = (unsigned char *) addrs[i];
    for (j = 0; j < lengths[i]; j+=4)
    {
      size_t k;

      fprintf(stream,"%*.*s", maxlen, maxlen, namefield);
      fprintf(stream,"%15p", (void *) p);
      for (k = 0; k < 4; k++)
      {
        fprintf(stream,"%3s%02x", " ", p[k]);
      }
      fprintf(stream, "    ");
      for ( k = 0; k < 4; k++)
      {
        if (isgraph(p[k]))
          fprintf(stream,"%c", p[k]);
        else
          fprintf(stream, ".");
      }
      fputc('\n', stream);
      namefield = " ";
      p += 4;
    }
    fputc('\n', stream);
  }
}
_

そしてそれを使用する方法の例:

_#include <stdio.h>

#include "dumper.h"

int main(void)
{
  int x = 0;
  double y = 3.14159;
  char foo[] = "This is a test";

  void *addrs[] = {&x, &y, foo, "This is a test"};
  char *names[] = {"x", "y", "foo", "\"This is a test\""};
  size_t lengths[] = {sizeof x, sizeof y, sizeof foo, sizeof "This is a test"};

  dumper(names, addrs, lengths, 4, stdout);

  return 0;
}
_
8
John Bode

「スタック」は文字の静的配列であるため、そのサイズは定義以来わかっているため、スタックに割り当てられ、関数が終了すると自動的に解放されます。 「heap」と「heap_string_malloc」はどちらもcharバッファーへのポインターとして宣言されており、コンテンツのサイズを定義するためにmallocを使用して動的に割り当てる必要がありますが、それがヒープメモリに常駐する理由です。行うことによって:

char *heap = "hello";

そして:

heap_string_malloc = "hello";

静的なバッファ値を使用して、ポインタ自体を変更しているのであり、ポインタが指しているコンテンツではありません。むしろmemcpyを使用して、「heap_string_malloc」ポインタが指すメモリをデータで変更する必要があります。

memcpy(heap_string_malloc, "hello", 5);
1
M. Dahmani

これにより、静的文字列「hello」のコピーを含む配列がスタックに作成されます。

char stack[] = "hello";

これにより、静的な文字列「hello」のアドレスを含むポインタがスタックに作成されます。

char *heap = "hello";

これにより、動的に割り当てられた5バイトのバッファのアドレスを含むポインタがスタックに作成されます。

char *heap_string_malloc = malloc(5);

しかし、3つのケースすべてで、スタックに何かを置きます。 char*は「ヒープ上」ではありません。これは、何かを指す(スタック上の)ポインターです。

1
jalf