ARM架构与C语言(韦东山)学习笔记(2)-全局变量,堆和栈


一、为什么全局变量没有被初始化

#include "stdio.h"
int g_a=123;
int add_val(volatile int v){
    
    
    volatile int a=321;
    v=v+a;
    return v;
}
int main(){
    
    
    static volatile int s_a=1;
    volatile int b;
    b=add_val(s_a);
    return 0;
}

这个函数里,如上节所讲,函数add_val(volatile int v)里的局部变量a被初始化,并分配到了栈空间里,但是为什么定义的全局变量int g_a=123和 static volatile int s_a=1;没有指令来初始化呢?

二、怎样初始化全局变量

对局部变量而言,初始化是分配指定字节的数据空间和指定字节的地址空间,局部变量是保存在RAM中的,而对于全局变量,全局变量在程序的任何地方都可以访问,因此它们通常会存储在RAM中。在程序启动时,全局变量会被初始化并分配内存空间。但是,如果我们在定义全局变量时使用了const关键字,或者将全局变量定义为static类型,那么这些变量通常会被存储在Flash中,而不是RAM中。这是因为const类型的变量通常被认为是常量,它们的值不会被修改,因此可以存储在Flash中。而static类型的变量是在程序运行期间只初始化一次,并在整个程序执行期间都存在的,因此也可以存储在Flash中。
对于flash中的全局变量,读写时会被copy到RAM中的指定地址去,这个地址是由编译器和系统共同决定的。
在这里插入图片描述
KEIL中,链接器LINKER会设置RAM基地址和FLASH基地址。所以,CPU先从flash中读取数据地址,把数据复制到R0寄存器中,那这个全局变量就被保存到RAM的0x20000000地址处了。

三、栈是什么

根据局部变量和全局变量的学习,大概知道了栈的概念:

Stack(栈)是计算机内存中的一种数据结构,它遵循“后进先出(Last In First Out,LIFO)”的原则。在栈中,数据的添加和移除只能发生在栈顶,因此最后添加到栈中的数据将首先被移除。
在计算机中,**栈通常用于存储临时数据,如函数调用时的参数、局部变量和返回地址等。**当一个函数被调用时,它的参数和局部变量会被分配在栈上,当函数返回时,这些数据会被从栈中移除。因此,栈提供了一种便捷的方式来管理函数调用和返回过程中的数据。
栈通常是由操作系统自动管理的,它使用指针来跟踪栈顶位置,当需要将数据压入栈中时,指针会向下移动;当需要弹出栈顶的数据时,指针会向上移动。由于栈的大小通常是固定的,因此在栈溢出时会导致程序崩溃。因此,在编写程序时,需要仔细管理栈的大小,避免发生栈溢出问题。

(1)栈的结构

对于ARM架构的cortex-m3内核,栈是向下生长的满栈,也就是说栈指针sp最初指向的是用户设置的高地址处,比如0x20001000处,由于向下增长,栈顶指针指向的地址是最新的数据所在的地址,而栈底指针指向的地址是最早的数据所在的地址。当栈满时,栈顶指针会指向栈底指针所在的地址,这时再添加数据就会导致栈溢出。
向下生长的栈“栈指针”寄存器的值指向栈顶的地址,每次压栈时它的值会减去压入数据的大小,这样就向下生长。而弹出栈顶的数据时,栈指针的值会增加,这样就向上移动。

(2)栈溢出

在这里插入图片描述
向下生成的满栈,如果初始化的函数里定义的变量过大,使栈指针不断向下,当栈满时,栈顶指针会指向栈底指针所在的地址,这时再添加数据就会导致栈溢出。

当栈溢出时,会发生以下后果:
程序崩溃:当栈溢出时,程序会不可避免地崩溃,这可能会导致数据丢失或者程序无法继续执行。
数据损坏:由于栈溢出会导致数据越界,因此栈中的数据可能会被覆盖或者损坏,这会导致程序无法正常工作。
安全漏洞:栈溢出也可能导致安全漏洞,攻击者可以通过向栈中添加恶意数据来控制程序的执行流程,从而执行恶意代码。
内存泄漏:如果程序中存在栈对象,当栈溢出时,这些对象可能无法正确地被释放,从而导致内存泄漏。

(3)如何避免栈溢出:估计栈的大小

目标是:寻找“使用局部变量最多”的调用链。这里采用GPT的答案:
1.观察函数调用链:栈的大小取决于函数调用的深度和每个函数的局部变量大小。因此,观察程序中函数的调用链和局部变量的大小可以大致估算栈的大小。
2.估算函数参数和返回值的大小:函数的参数和返回值也会占用栈空间。因此,估算函数参数和返回值的大小可以帮助估算栈的大小。
3.考虑递归:如果程序中存在递归函数,需要考虑递归的深度和局部变量的大小,以估算栈的大小。
4.考虑异常处理:如果程序中使用了异常处理,需要考虑异常处理的堆栈大小,以估算栈的大小。
5.使用栈溢出检测工具:一些编译器和调试工具提供了栈溢出检测功能,可以通过这些工具检测程序中可能出现的栈溢出问题,从而优化栈的大小。

四、堆是什么

GPT解释:
堆(Heap)是一种动态分配内存的方式,它是操作系统为程序运行时分配内存的一块区域,用于存储程序运行时需要动态分配的数据。堆内存是由程序员自己管理的,程序员可以通过调用操作系统或编程语言提供的堆管理函数(如malloc、new等)来分配和释放堆内存。堆内存的大小通常是动态的,可以根据程序的需要进行扩展和收缩。
堆内存的特点如下:
动态分配:堆内存可以根据程序的需要动态分配,程序员可以根据实际情况分配所需的内存大小。
程序员管理:堆内存的管理由程序员自己负责,程序员需要手动调用堆管理函数来分配和释放堆内存。
多线程共享:堆内存可以被多个线程共享,因此需要考虑线程安全的问题。
内存泄漏:由于堆内存的管理由程序员自己负责,因此存在内存泄漏的风险,程序员需要注意及时释放不再使用的堆内存。
内存碎片:堆内存的动态分配和释放容易导致内存碎片问题,程序员需要采取一些优化措施来避免内存碎片的问题。
堆内存通常用于存储动态数据结构(如链表、树等)和大量的数据(如图像、音频等),因为这些数据的大小通常是不确定的,需要动态分配内存来存储。

1.示例函数

#include "stdio.h"
#include "stdlib.h"

int heap(){
    
    
char *str;
str = malloc(100);
strcpy(str,"helloworld");
free(str);
}

我们知道,对一个函数,当执行完里面的定义变量语句后,这个变量的空间便被释放了。那对于指定的栈顶到栈底,还有很大的一块空间,当不创建栈时,就是一块空闲空间,于是便可以利用malloc()来指定一块空闲空间,这块空闲空间即不会影响栈空间的分配,也不会重叠到底部的全局变量上去,因此可以来完成一些动态分配内存空间操作

2.char *str;和char str的区别

char str声明的是一个字符指针变量,即str指向的是一个内存地址,存储的是指向一个字符数组的指针。也就是说str是一个指针。

而char str声明的是一个字符数组变量,即str存储的是一段字符数组的内容。

具体来说,**char *str的使用场景通常是在需要动态分配内存空间的情况下,例如使用malloc函数分配一个字符串空间,并将其地址赋给str,然后通过str指针对字符串进行操作 **。而char str通常用于声明固定长度的字符数组,例如char str[100]表示声明了一个长度为100的字符数组,可以存储最多100个字符。
例如:

// 使用 char *str 定义字符串变量
char *str = (char*) malloc(100 * sizeof(char)); // 动态分配内存空间
strcpy(str, "Hello, world!"); // 复制字符串
printf("%s\n", str); // 输出字符串
free(str); // 释放内存空间

// 使用 char str[100] 定义字符串变量
char str[100] = "Hello, world!"; // 直接初始化字符数组
printf("%s\n", str); // 输出字符串

3.malloc()和free()

malloc 和 free 是 C 语言中的动态内存分配和释放函数。

malloc 函数用于在堆内存中动态分配一块指定大小的内存空间,并返回一个指向这个空间的指针。其函数原型如下:

void *malloc(size_t size);

其中,size 参数表示需要分配的内存空间大小,以字节为单位。malloc 函数返回一个 void* 类型的指针,指向分配的内存空间的起始地址。如果分配失败,则返回空指针 NULL。

free 函数用于释放之前使用 malloc 函数分配的内存空间,其函数原型如下:

void free(void *ptr);

其中,ptr 参数表示需要释放的内存空间的指针,即指向由 malloc 函数返回的指针。调用 free 函数后,该内存空间将被释放,并可以被重新分配给其他变量使用。

综上所述,堆就是一块空闲内存,可以使用malloc或者free函数来管理它。

五、为什么堆栈常常一起称呼?堆和栈的区别?

堆和栈是计算机内存管理中两种重要的数据结构,它们常常一起被提到,因为它们都是用来管理内存空间的。
但是,我觉得堆和栈是不同的,不应该一起叫做堆栈。堆就是堆,栈就是栈。

堆和栈的区别
1.分配方式:栈空间由系统自动分配和管理,而堆空间需要由程序员手动分配和释放。
2.空间大小:栈空间的大小是固定的,由系统预先设定,而堆空间的大小不固定,可以动态地分配和释放。
3.分配效率:由于栈空间的分配和释放是由系统自动完成的,因此分配和释放的效率比堆空间更高。
4.存储方式:栈空间采用先进后出(Last In First Out,LIFO)的方式存储数据,主要用于存储函数调用时的参数、局部变量以及函数返回地址等信息;而堆空间则没有特定的存储方式,用于存储动态分配的数据结构,如动态数组、链表等。
5.生命周期:栈空间的生命周期与函数调用的生命周期相同,函数调用结束后,栈空间中的数据也会被自动释放;而堆空间的生命周期由程序员手动管理,需要在不需要使用堆空间时手动释放空间。

六、静态变量会入栈吗?

不会。
静态变量在内存中的存储位置是在程序的数据区(data segment)中,而不是在栈中。因此,静态变量不会入栈。
**程序的数据区是在程序运行时由系统分配的一块内存空间,用于存储全局变量、静态变量和常量等数据。**数据区的大小是固定的,在编译时就已经确定,因此静态变量的空间大小也是固定的,不会随着函数的调用而动态变化。
相比之下,栈是一种动态的内存空间,用于存储函数调用时的参数、局部变量和返回地址等信息。栈空间的大小是有限的,通常只有几百 KB 到几 MB,因此栈中存储的变量的空间大小和数量都有一定的限制。

七、如何去指定堆具体在某一段空间?

如果需要将堆空间分配到指定的地址段,可以使用指针类型转换和指针运算等技巧实现。

下面是一个示例代码,演示如何手动分配一块大小为 10 个整型变量的堆空间,并将其分配到指定的地址段:

#include <stdio.h>
#include <stdlib.h>

int main() {
    
    
    int *ptr, *start_addr;
    int size = 10;
    // 分配堆空间
    ptr = (int*)malloc(size * sizeof(int));
    
    // 将堆空间分配到指定的地址段
    start_addr = (int*)0x10000; // 假设指定地址为 0x10000
    ptr = start_addr;
    
    // 释放堆空间
    free(ptr);
    return 0;
}

在上面的代码中,首先通过 malloc 函数分配了一块大小为 10 个整型变量的堆空间,然后将指针 ptr 指向这块堆空间的起始地址。接着,通过将指针 ptr 指向指定的地址段实现了将堆空间分配到指定的地址段。通过 free 函数释放了堆空间。

猜你喜欢

转载自blog.csdn.net/qq_53092944/article/details/131029319