C语言面试之旅:掌握基础,探索深度(面试实战之c语言内存上篇)

         人生如路,要有耐心。要想快乐地生活,就要学会一切随缘,不强求不可得,不执着已失去,淡定悠然,随遇而安。烦恼好比心中的黑暗,只有点亮随缘的心灯,才能驱散黑暗,照亮人生。        

                                                                                                                                          ----小新

一.内存分配方式

     1.全局存储区域(静态存储区)

        这种分配方式由编译器自动完成,在程序编译时就已经分配好内存。例如,全局变量和static变量就是在静态存储区域进行分配的。这种方式的优点是内存空间大小固定,且在整个程序运行期间都存在,直到程序运行结束时才被释放。

int global_var = 10; // 全局变量在编译时就已经分配好内存,整个程序运行期间都存在

     2.在栈上分配

        这种方式也由编译器自动完成。当函数被调用时,函数内部的局部变量都可以在栈上创建。当函数执行结束时,这些存储单元会被自动释放。这种方式的优点是分配和释放速度快,但是分配的内存容量有限。

void func() { // 函数执行时在栈上创建局部变量,执行结束时自动释放  
    int local_var = 20; // 局部变量在栈上分配内存  
    // 函数执行结束,局部变量自动释放  
}

      3.从堆上分配

        这种方式也称为动态内存分配,它由程序员手动完成。程序员使用内存分配函数(如malloc或new)来申请任意大小的内存,使用完之后再由程序员自己负责使用内存释放函数(如free或delete)来释放内存。这种方式的优点是可以根据需要动态地申请和释放内存,但是需要程序员手动管理,容易造成内存泄漏。

int* ptr = NULL; // 指向动态内存的指针  
ptr = (int*)malloc(sizeof(int)); // 在堆上分配内存,如果分配失败则返回NULL  
if (ptr == NULL) {  
    printf("Memory allocation failed!\n");  
    return -1; // 返回错误代码  
}  
*ptr = 30; // 写入动态内存的值  
printf("The value of ptr is: %p\n", ptr); // 输出动态内存的地址  
printf("The value of *ptr is: %d\n", *ptr); // 输出动态内存的值  
free(ptr); // 释放动态内存,防止内存泄漏

二.堆和栈的区别

        1.申请方式

                栈:由系统自动分配;

                堆:人为申请开辟。

        2.空间大小

                栈:获得的空间较小,一般为几MB;

                堆:获得的空间较大。

        3.申请效率

                栈:由系统自动分配,速度较快;

                堆:要依靠malloc等API申请,申请的过程比较复杂,速度比较慢。

        4.存储内容

                栈:在函数调用时,函数调用语句的下一条可执行语句的地址第一个进栈,然后函数的各个参数进栈,其中静态变量是不入栈的,静态变量存放在数据段;

                堆:一般是在头部用一个字节存放堆的大小,堆中的具体内容是人为安排。

        5.底层

                栈:连续的空间;

                堆:不连续的空间,堆可能有许多内存碎片。

 

三.栈在c语言中的作用

  1.保存局部变量和函数参数

        在函数调用过程中,局部变量和函数参数会被存储在栈上。当函数执行结束后,这些变量和参数会被自动释放,从而为后续的函数调用腾出空间。

  2.实现函数调用和返回

        在C语言中,函数调用时会在栈上为其分配一个新的栈帧,包含函数的局部变量、参数和返回地址。当函数执行结束时,会从栈上弹出该栈帧,回到函数调用前的状态。

  3.实现递归

        递归函数会在每次递归调用时在栈上分配一个新的栈帧,从而实现了函数的嵌套调用。当递归结束时,会逐层返回,逐个释放栈帧。

  4.实现动态内存分配

        C语言中的动态内存分配通常使用堆来实现,但栈上也存在一种特殊的动态内存分配方式,即使用alloca函数在栈上分配内存。这种方式比堆分配要快,但可能会导致栈溢出或内存碎片问题。

 四.内存泄漏

        简单来说就是申请了内存,不使用之后并没有释放内存,或者说,指向申请的内存的指针突然又去指向别的地方,导致找不到申请的内存,

  (1)影响

        随着程序运行时间越长,占用内存越多,最终用完内存,导致系统崩溃。

  (2)减少内存泄漏

        1.良好的编码习惯,使用内存分配的函数,一但使用完毕之后就要记得使用对应的函数是否掉;

        2.将分配的内存的指针以链表的形式自行管理,使用之后从链表中删除,程序结束时可以检查改链表;

        3.使用智能指针。

 五.字节对齐问题

        数据在内存中的存储位置(即地址)是按照某个特定的规则进行对齐的。这个规则通常由计算机硬件和操作系统定义。对齐的目的是为了提高内存的访问效率,因为CPU通常一次只能读取固定大小的内存,如果数据没有对齐,就需要多次读取才能获取完整的数据。

        字节对齐问题通常出现在低级编程语言如C/C++中,因为这些语言直接操作内存,需要程序员自己管理数据的存储。而在高级语言中,编译器通常会自动处理字节对齐的问题。

解决字节对齐问题的方法通常包括以下几种:

  1.数据类型对齐

        在定义数据结构或变量时,可以使用特定的数据类型来保证字节对齐。例如,在C++中,可以使用alignas关键字来指定数据的对齐方式。

  2.填充

        在某些情况下,可以在数据之间插入填充字节,以保证数据的对齐。这种方法通常用于处理跨多个内存地址的数据结构。

  3.使用特定的编译器指令或属性

        某些编译器提供了特定的指令或属性来控制字节对齐的方式。例如,在C++中,可以使用#pragma pack指令来控制数据结构的对齐方式。

        总之,字节对齐问题是一个与内存管理和数据访问效率密切相关的问题。在编写低级语言或处理性能敏感的代码时,需要特别关注字节对齐问题。

你可能感兴趣的:(c语言,面试,开发语言)