为指针及其数据分配的内存在哪里?

Posted

技术标签:

【中文标题】为指针及其数据分配的内存在哪里?【英文标题】:where is memory allocated for pointers and their data? 【发布时间】:2016-01-01 09:16:28 【问题描述】:

我的问题是我是否有一些功能

void func1()
    char * s = "hello";
    char * c;
    int b;
    c = (char *) malloc(15);
    strcpy(c,s);

我认为 s 指针是在堆栈上分配的,但是存储在程序数据段中的数据“hello”在哪里?至于 c 和 b 它们是统一化的,因为'c = some memory address'并且它还没有它是如何工作的?和 b 也没有内容,所以它不能存储在堆栈中? 那么当我们用 malloc 在堆上为 c 分配内存时,c 现在有了一些内存地址,这个统一化的 c 变量是如何给定堆上那个字符串的第一个字节的地址的呢?

【问题讨论】:

C 标准没有指定对象的存储方式/存储位置,仅指定存储持续时间。 "hello" 将存储在 DS 中。根据上下文,指针可以在堆栈或数据上。但b 将在堆栈上。 大多数 C 编译器都会将它存储在文本段中。允许程序更改文字没有多大意义。它不是 const char* 是一个古老的 C 错误,无法修复,因为它会破坏大多数现有程序 :) 尝试将其更改为甜甜圈,它会说 bang! @HansPassant:实际上,通常是 read-only DS 放置字符串文字。除此之外,你所说的一切仍然适用。 【参考方案1】:

我们需要考虑一个变量的内存位置和它的内容是什么。请记住这一点。

对于 int,变量有一个内存地址,并且有一个数字作为其内容。

对于 char 指针,变量有一个内存地址,其内容是一个指向字符串的指针——实际的字符串数据在另一个内存位置。

要理解这一点,我们需要考虑两件事:

(1)程序的内存布局
(2)函数被调用时的内存布局

程序布局[典型]。低内存地址到高内存地址:

代码段——指令所在的位置:
  ...
  func1 的机器指令
  ...
数据段——初始化的全局变量和常量所在的位置:
  ...
  int myglobal_inited = 23;
  ...
  “你好”
  ...
bss 段——用于未初始化的全局变量:
  ...
  int myglobal_tbd;
  ...
堆段——存储 malloc 数据的地方(向上增长到更高的内存
地址):
  ...
堆栈段——从顶部内存地址开始,向末端向下增长
堆

现在这是一个函数的堆栈帧。它将在某个堆栈段内。注意,这是内存地址越高越低:

函数参数[如果有的话]:
  arg2
  参数1
  参数0
函数的返回地址[返回时会去哪里]
函数的堆栈/局部变量:
  字符 *s
  字符 *c
  诠释 b
  字符缓冲区[20]

请注意,我添加了一个“buf”。如果我们将 func1 更改为返回字符串 pointer(例如“char *func1(arg0,arg1,arg2)”并且我们添加了“strcpy(buf,c)”或“strcpy(buf,c)” func1 可以使用 buf。func1 可以返回 c 或 s,但 不是 buf。

这是因为使用“c”时,data 存储在数据段中,并在 func1 返回后持续存在。同样,可以返回 s,因为数据在堆段中。

但是,buf 不起作用(例如返回 buf),因为数据存储在 func1 的堆栈帧中,并且在 func1 返回时从堆栈中弹出[这意味着它对调用者来说将显示为垃圾]。换句话说,给定函数的堆栈帧中的数据对它以及它可能调用的任何函数都是可用的[等等......]。但是,该堆栈框架可供该函数的调用者使用。也就是说,堆栈帧数据仅在被调用函数的生命周期内“保留”。

这是完全调整后的示例程序:

int myglobal_initialized = 23;
int myglobal_tbd;

char *
func1(int arg0,int arg1,int arg2)

    char *s = "hello";
    char *c;
    int b;
    char buf[20];
    char *ret;

    c = malloc(15);
    strcpy(c,s);

    strcpy(buf,s);

    // ret can be c, s, but _not_ buf
    ret = ...;

    return ret;

【讨论】:

【参考方案2】:

让我们将这个答案分成两个观点,因为标准只会使对该主题的理解复杂化,但无论如何它们都是标准:)。

两部分的共同主题

void func1() 
    char *s = "hello";
    char *c;
    int b;

    c = (char*)malloc(15);
    strcpy(c, s);

第一部分:从标准的角度来看

根据标准,有一个有用的概念称为自动变量持续时间,其中变量的空间在进入给定范围时自动保留(具有统一的值,又名:垃圾!),它可以在这样的范围内设置/访问或不访问,并且释放这样的空间以供将来使用。 注意:在 C++ 中,这也涉及到对象的构造和销毁。

因此,在您的示例中,您有三个自动变量:

char *s,它被初始化为 "hello" 的地址。 char *c,它保存垃圾直到它被稍后的赋值初始化。 int b,它在其整个生命周期中都保存着垃圾。

顺便说一句,标准未指定存储如何与函数一起使用。

第二部分:从现实世界的角度来看

在任何体面的计算机架构上,您都会发现一种称为堆栈的数据结构。堆栈的目的是保存可以被自动变量使用和回收的空间,以及一些空间用于递归/函数调用所需的一些东西,并且如果编译器可以用作保存临时值(用于优化目的)的地方决定。

堆栈以PUSH/POP 方式工作,即堆栈向下增长。让我更好地解释一下。想象一个像这样的空栈:

[Top of the Stack]
[Bottom of the Stack]

如果你,例如,PUSHint 的值 5,你会得到:

[Top of the Stack]
5
[Bottom of the Stack]

那么,如果你PUSH-2

[Top of the Stack]
5
-2
[Bottom of the Stack]

而且,如果您POP,您检索-2,堆栈看起来就像之前-2PUSHed。

栈底是一个屏障,可以向上移动到PUSHing 和POPing。在大多数架构上,堆栈的底部由称为 堆栈指针 的processor register 记录。将其视为unsigned char*。你可以减少它,增加它,对它进行指针运算等等。一切都是为了对堆栈的内容进行黑魔法。

在堆栈中为自动变量保留(空间)是通过减少它来完成的(记住,它是向下增长的),而释放它们是通过增加它来完成的。基于此,之前的理论PUSH -2 是伪汇编中类似这样的简写:

SUB %SP, $4    # Subtract sizeof(int) from the stack pointer
MOV $-2, (%SP) # Copy the value `-2` to the address pointed by the stack pointer

POP whereToPop 只是反过来

MOV (%SP), whereToPop # Get the value
ADD %SP, $4           # Free the space

现在,编译 func1() 可能会产生以下伪汇编(注意:您不会完全理解这一点):

.rodata # Read-only data goes here!
.STR0 = "hello" # The string literal goes here

.text # Code goes here!
func1:
    SUB %SP, $12     # sizeof(char*) + sizeof(char*) + sizeof(int)
    LEA .STR0, (%SP) # Copy the address (LEA, load effective address) of `.STR0` (the string literal) into the first 4-byte space in the stack (a.k.a `char *s`)
    PUSH $15         # Pass argument to `malloc()` (note: arguments are pushed last to first)
    CALL malloc
    ADD %SP, 4       # The caller cleans up the stack/pops arguments
    MOV %RV, 4(%SP)  # Move the return value of `malloc()` (%RV) to the second 4-byte variable allocated (`4(%SP)`, a.k.a `char *c`)
    PUSH (%SP)       # Second argument to `strcpy()`
    PUSH 4(%SP)      # First argument to `strcpy()`
    CALL strcpy
    RET              # Return with no value

我希望这对你有所启发!

【讨论】:

以上是关于为指针及其数据分配的内存在哪里?的主要内容,如果未能解决你的问题,请参考以下文章

数组及其在内存中的分配

数组及其在内存中的分配

动态内存——动态内存与智能指针

常见的内存错误及其对策

C指针及其运算

C指针及其运算