为啥 char[] 在堆栈上,而 char * 在堆上?

Posted

技术标签:

【中文标题】为啥 char[] 在堆栈上,而 char * 在堆上?【英文标题】:Why is char[] on the stack but char * on the heap?为什么 char[] 在堆栈上,而 char * 在堆上? 【发布时间】:2013-11-08 11:28:55 【问题描述】:

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

【问题讨论】:

char *heap_string_malloc = malloc(5); heap_string_malloc = "hello"; 是一个 insta-memory-leak。这不是 Java。考虑一下:int a=5; a=6; 5 去哪儿了? 数组和指针是一回事。 "hello" 也输入char []。但是char * 可以很好地指出它。 @EliasVanOotegem 不在全局空间中,不,不会。在作为自动函数的本地空间中,它会很好地编译(并且如上所述泄漏内存)。您收到的警告可能是因为将 const char[] 地址分配给非 const char* 指针,最近的 C 编译器会警告您(例如,我的 clang 会警告您)。 @WhozCraig:我的错误......我检查了我的代码,我得到的错误更加愚蠢(在函数MyStruct **str_params我试图按照str_params->some_char_arr = some_char_ptr ) 显然没有用 【参考方案1】:

诸如"hello" 之类的字符串文字的存储方式使其在程序的整个生命周期内都保持不变。它们通常存储在单独的数据段(不同于堆栈或堆)中,可能是只读的。

当你写作时

char stack[] = "hello";

您正在创建一个类型为“char”的新 auto ("stack") 变量(大小取自字符串文字的长度),以及 内容字符串文字 "hello" 的 em> 被复制到它。

当你写作时

char *heap = "hello";

您正在创建一个类型为“指向char”的新auto ("stack") 变量,并将字符串文字"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 开始。 stack ahd heap 变量 都被创建为 auto(“堆栈”)变量。 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;

【讨论】:

如何从他们自己的程序中打印出这些信息?【参考方案2】:

这会在堆栈上创建一个数组,其中包含静态字符串“hello”的副本:

char stack[] = "hello";

这会在堆栈上创建一个指针,其中包含静态字符串“hello”的地址:

char *heap = "hello";

这会在堆栈上创建一个指针,其中包含一个动态分配的 5 字节缓冲区的地址:

char *heap_string_malloc = malloc(5);

但是在这三种情况下,你都在堆栈上放了一些东西。 char* 不是“在堆上”。它是一个指针(在堆栈上),它指向某处的某物。

【讨论】:

【参考方案3】:

"stack" 是一个静态字符数组,因此它将在堆栈中分配并在函数结束后自动释放,因为它的大小自定义以来就已知。 虽然 "heap" 和 "heap_string_malloc" 都被声明为指向 char 缓冲区的指针,并且需要使用 malloc 动态分配以定义其内容的大小,但这就是它们将驻留在堆内存中的原因。 通过这样做:

char *heap = "hello";

和:

heap_string_malloc = "hello";

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

memcpy(heap_string_malloc, "hello", 5);

【讨论】:

【参考方案4】:

当你这样做时,例如

char *heap = "hello";

指针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 获得相同地址的原因是因为它们都指向相同的文字字符串。

【讨论】:

【参考方案5】:

Arrays are not pointers。您的程序正在逐行执行的是

// 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 当然,包含"hello" 的两个静态缓冲区位于同一个地址是完全合理的,所以heap_string_mallocheap 很可能指向同一个地方。 如果他没有heap_string_malloc 指向“hello”,而是指向不同的字符串“hola”,这仍然是内存泄漏吗? +1 那篇文章很好地说明了一个简单的事实,即指针是 变量 持有地址;数组是地址的变量。 @user2018675 当然,C 语言中的 "hello" 没有什么神奇之处 :) @user2018675 是的,因为他仍然没有任何指向 malloc 分配的内存的东西。我喜欢用狗的比喻来解释它。我从一家商店买了一只狗,带他去公园,他连着一根绳子(给你们美国人用皮带……)。在公园的时候,我把他从绳子上放了下来,把绳子接到别人的狗上,然后回家了。我还有一条狗,但不是我的狗。 “狗”是 malloc 返回的。 “领先”是 heap_string_malloc。另一只狗是“你好”。

以上是关于为啥 char[] 在堆栈上,而 char * 在堆上?的主要内容,如果未能解决你的问题,请参考以下文章

可以/为啥在返回类型中使用 char * 而不是 const char * 会导致崩溃?

为啥在 C++ 中更喜欢 char* 而不是字符串?

为啥 putchar、toupper、tolow 等采用 int 而不是 char?

为啥我应该使用 char 而不是 varchar? [复制]

在 C 中,为啥 sizeof(char) 为 1,而 'a' 是 int?

Char* 在函数中使用 malloc 创建,编译器说地址在堆栈上,无法返回