为什么char[]在栈上而char*在堆上?

37

我对正在发生的事情感到非常困惑。 我一直以为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)。


11
char *heap_string_malloc = malloc(5); heap_string_malloc = "hello"; 这是一个即时内存泄漏。这不是Java。考虑一下:int a=5; a=6; 那么5去哪了? - WhozCraig
4
数组和指针不是同一回事。 - Blagovest Buyukliev
“hello”也是一个类型为char []的字符数组。但是,一个char *指针同样可以指向它。 - Sadique
@EliasVanOotegem 如果不在全局空间中,那么它不会。在函数的本地空间中作为自动变量,它肯定会编译得很好(并像描述的那样泄漏内存)。您收到的警告可能是因为将const char[]地址分配给非const char*指针,最近的C编译器会对此发出警告(例如我的clang)。 - WhozCraig
@WhozCraig:我的错误… 我检查了我的代码,我得到的错误甚至更愚蠢(在一个函数 MyStruct **str_params 中,我试图赋值类似于 str_params->some_char_arr = some_char_ptr 的东西),显然没有起作用。 - Elias Van Ootegem
5个回答

44

数组不是指针。你的程序逐行执行的内容如下:

// 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";

你看到两次相同的指针是因为编译器优化掉了第二个包含“hello”的静态缓冲区。

1
+1 当然,两个包含“hello”的静态缓冲区位于同一地址是完全合理的,因此heap_string_mallocheap很可能指向同一位置。 - mjs
如果他没有将 heap_string_malloc 指向 "hello",而是指向另一个字符串 "hola",那么这仍然会造成内存泄漏吗? - user2018675
+1 那篇文章非常清楚地阐明了一个简单的事实,即指针是变量,它们保存地址;而数组是地址的变量。 - WhozCraig
1
@user2018675 当然,在 C 语言中,"hello" 并没有什么神奇的地方 :) - Fred Foo
3
@user2018675,是的,因为他仍然没有任何指向由malloc分配的内存的内容。我喜欢用狗的比喻来解释。我从商店买了一只狗,带着牠连接在链条上去公园(对美国人来说是leash) 。在公园里,我放开了牠,把链条连接到别人的狗身上,然后回家了。我现在有一只狗,但不是我的那只狗。这个"狗"就是malloc返回的内存。而"链条"则是heap_string_malloc。另一只狗是"hello"。 - mjs
显示剩余6条评论

17

当你执行例如

char *heap = "hello";

指针heap实际上并不指向堆,而是指向由操作系统加载程序的静态数据。实际上,正确的说法应该是:

指针heap实际上并不指向动态分配的内存区域(堆),而是指向由操作系统加载程序时一起加载的静态数据区域。

const char *heap = "hello";

由于heap指向的是一个常量只读的内存块。


此外,虽然数组可以衰减为(并可用作)指针,并且指针可以使用数组语法,但它们不是相同的。最大的区别在于,对于数组,您可以使用例如sizeof来获取实际数组的字节数,而对于指针则不可能。


第三点,当你执行

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

你有一个内存泄漏,因为你首先将某些内容分配给了heap_string_malloc,但紧接着又将heap_string_malloc重新分配为指向完全不同的东西。


至于你获得相同地址的原因是heapheap_string_malloc都指向同一字面字符串。


13

字符串字面量(如"hello")存储在程序整个生命周期中,通常被存储在一个单独的数据段(与堆栈或堆不同),该数据段可能是只读的。

当您编写代码时:

char stack[] = "hello";

你正在创建一个新的auto(“stack”)变量,其类型为“6个元素的char数组”(大小取决于字符串文字的长度),并且字符串文字"hello"的内容被复制到该变量中。

当你编写:

char *heap = "hello";

你正在创建一个类型为“指向char的指针”的新的auto(“堆栈”)变量,然后将字符串字面值"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   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开始。 stackheap变量都被创建为auto ("stack")变量。stack包含字符串常量内容的一个副本,而heap包含字符串常量的地址。
现在,假设我使用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 变量现在包含不同的地址,该地址指向另一个含有字符串 "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;
}

如何从自己的程序中打印出这些信息? - user1422456

1
这将在堆栈上创建一个数组,其中包含静态字符串"hello"的副本:
char stack[] = "hello";

这将在堆栈上创建一个指针,其中包含静态字符串“hello”的地址:
char *heap = "hello";

这将在堆栈上创建一个指针,其中包含动态分配的 5 字节缓冲区的地址:
char *heap_string_malloc = malloc(5);

但在这三种情况下,你会将某些东西放在堆栈上。 char*并不是“在堆上”的。它是一个指向某个地方的指针(在堆栈上)。

0
“stack”是一个char静态数组,因此它将被分配在堆栈中,并且一旦函数结束,它将自动释放,因为其大小自定义时就已知。 而“heap”和“heap_string_malloc”都声明为char缓冲区的指针,需要使用malloc进行动态分配以定义它们内容的大小,这就是它们将驻留在堆内存中的原因。 通过执行以下操作:
char *heap = "hello";

并且:

heap_string_malloc = "hello";

你正在修改指针本身(使用静态缓冲区值),而不是它们所指向的内容。你应该使用memcpy函数,用你的数据修改由“heap_string_malloc”指针指向的内存:

memcpy(heap_string_malloc, "hello", 5);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接