嵌入式面经篇四——内存管理


前言

记录一些招聘公司在招聘嵌入式软件岗位时的一些问题,此文为第四篇。


一、内存管理&编程题

1、由gcc编译的C语言程序占用的内存分为哪几个部分?

在这里插入图片描述

2、大小端

小端:一个数据的低位字节数据存储在低地址
大端:一个数据的高位字节数据存储在低地址

例如:int a=0x12345678; //a首地址为0x200,大端存储格式如下:

数据0x120x340x560x78
地址0x2000x2010x2020x203

如何判读一个系统的大小端存储模式?

  • 方法一:int * 强制类型转换为 char *,用 “[]” 解引用
    void checkCpuMode(void)  
    {  
        int c = 0x12345678;  
        char *p = (char *)&c;  
        if(p[0] == 0x12)  
            printf("Big endian.\n");  
        else if(p[0] == 0x78)  
            printf("Little endian.\n");  
        else  
            printf("Uncertain.\n");  
    } 
    
  • 方法二:int *强制类型转换为char ,用“”解引用
    void checkCpuMode(void)  
    {  
        int c = 0x12345678;  
        char *p = (char *)&c;  
        if(*p == 0x12)  
            printf("Big endian.\n");  
        else if(*p == 0x78)  
            printf("Little endian.\n");  
        else  
            printf("Uncertain.\n");  
    } 
    
  • 方法三:包含 short 跟 char 的共用体
    void checkCpuMode(void)  
    {  
        union Data  
        {  
            short a;  
            char b[sizeof(short)];  
        }data;  
        data.a = 0x1234;  
      
        if(data.b[0] == 0x12)  
            printf("Big endian.\n");  
        else if(data.b[0] == 0x34)  
            printf("Little endian.\n");  
        else  
            printf("uncertain.\n");  
    } 
    

3、全局变量和局部变量的区别?

  • 全局变量储存在静态区,进入 main 函数之前就被创建,生命周期为整个源程序。
  • 局部变量在栈中分配,在函数被调用时才被创建,在函数退出时销毁,生命周期为函数内。

4、以下程序中,主函数能否成功申请到内存空间?

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
void getmemory(char *p)  
{  
    p = (char *)malloc(100);  
    strcpy(p, "hello world");  
}  
int main()  
{  
    char *str = NULL;  
    getmemory(str);  
    printf("%s\n", str);  
    free(str);  
    return 0;  
} 

答:不能。
解读:getmemory(str) 没能改变 str 的值,因为传递给子函数的只是 str 的复制值 NULL,main 函数中的 str 一直都是 NULL。正确的 getmemory() 如下:

①传递的是二重指针,即str的指针
void getmemory(char **p)   
{  
    *p = (char *)malloc(100);  
    strcpy(*p, "hello world");  
}  
②传递的是指针别名,即str的别名,C++void getmemory(char * &p)   
{  
    p = (char *)malloc(100);  
    strcpy(p, "hello world");  
}  

5、请问运行下面的 Test() 函数会有什么样的后果?

void GetMemory(char **p, int num)  
{  
    *p = (char *)malloc(num);  
}  
void Test(void)  
{  
    char *str = NULL;  
    GetMemory(&str, 100);  
    strcpy(str, "hello");   
    printf("%s\n", str);   
}  

答:内存泄漏。
解读:调用 malloc() 申请内存空间,使用完毕之后没有调用 free() 释放内存空间并使指针指向 NULL。

6、 请问运行下面的 Test() 函数会有什么样的后果?

char *GetMemory(void)  
{   
    char p[] = "hello world";  
    return p;  
}  
void Test(void)  
{  
    char *str = NULL;  
    str = GetMemory();   
    printf("%s\n", str);  
}  

答:打印野指针内容,可能是乱码。
解读:GetMemory() 返回的是指向栈内存的指针,但该栈内存已被释放,该指针的地址不是 NULL,成为野指针,新内容不可知。

7、请问运行下面的 Test() 函数会有什么样的后果?

void Test(void)  
{  
    char *str = (char *) malloc(100);  
    strcpy(str,"hello");  
    free(str);       
    if(str != NULL)  
    {  
        strcpy(str, "world");   
        printf("%s\n", str);  
    }  
}  

答:篡改堆区野指针指向的内容,后果难以预料,非常危险。
解读:

  • free(str); 之后,str 成为野指针,没有置为 NULL,if(str != NULL) 语句不能阻止篡改操作。
  • 野指针不是 NULL指针,是指向被释放的或者访问受限的内存的指针。
  • 造成野指针原因:①指针变量没有被初始化,任何刚创建的指针不会自动成为NULL;②指针被free或delete之后,没有置NULL;③指针操作超越了变量的作用范围,比如要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。

8、在C语言中 memcpy 和 memmove 是一样的吗?

答:

  • memcpy() 与 memmove() 一样都是用来拷贝 src 所指向内存内容前 n 个字节到 dest 所指的地址上。
  • 不同的是,当 src 和 dest 所指的内存区域重叠时,memcpy 可能无法正确处理,而 memmove() 仍然可以正确处理,不过执行效率上略慢些。

解读:

  • memcpy() 无论什么情况下,都是从前往后拷贝内存。当源地址在前,目的地址在后,且两个区域有重叠时,会造成拷贝错误,达不到理想中的效果。
void *memcpy(void *dest, const void *src, size_t count)  
{  
    if(dest == NULL || src == NULL || count <= 0)  return NULL;  
    char *d = (char *)dest;  
    char *s = (char *)src;  
    while(count--)  
    {  
        *d++ = *s++;  
    }  
    return dest;  
}  
  • memmove() 则分两种情况:目的地址在前,源地址在后的情况下,从前往后拷贝内容。否则从后往前拷贝内容。无论什么情况都能达到理想中的效果。
void *memmove(void *dest, const void *src, size_t count)  
{  
    if(dest == NULL || src == NULL || count <= 0)  return NULL;  
    if(dest < src)  
    {  
        char *d = (char *)dest;  
        char *s = (char *)src;  
        while (count--)  
        {  
            *d++ = *s++;  
        }  
    }  
    else  
    {  
        char *d = (char *)dest + count;  
        char *s = (char *)src + count;  
        while (count--)  
        {  
            *--d = *--s;  
        }  
    }      
    return dest;  
}  

9、malloc 的底层是如何实现的?

  • malloc 函数的底层实现是操作系统有一个由可用内存块连接成的空闲链表。调用 malloc 时,它将遍历该链表寻找足够大的内存空间,将该块一分为二(一块与用户申请的大小相等,另一块为剩下来的碎片,会返回链表),调用 free 函数时,内存块重新连接回链表。
  • 若内存块过于琐碎无法满足用户需求,则操作系统会合并相邻的内存块。

10、在1G内存的计算机中能否通过malloc申请大于1G的内存?为什么?

答:可以。因为 malloc 函数是在程序的虚拟地址空间申请的内存,与物理内存没有直接的关系。虚拟地址与物理地址之间的映射是由操作系统完成的,操作系统可通过虚拟内存技术扩大内存。

11、内存泄漏是什么?

  • 内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
  • 分类
    • 常发性内存泄漏:发生泄漏的代码会被多次执行到。
    • 偶发性内存泄漏:发生泄漏的代码在某些环境或操作下才会发生。
    • 一次性内存泄漏:只会被执行一次。
    • 隐式内存泄漏:程序在运行过程中不停地分配内存,直到结束时才释放。严格来讲这不算内存泄漏,但服务器运行时间很长,可能会耗尽所有内存。

12、内存溢出是什么?与内存泄漏有何关系?

  • 内存溢出(Out Of Memory)是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于系统能提供的最大内存。此时程序无法运行,系统提示内存溢出。有时候会自动关闭软件。
  • 造成内存溢出的原因:
    • 内存泄漏的堆积最终导致内存溢出。
    • 需要保存多个耗用内存过大的对象或加载单个超大的对象时,其大小超过了当前剩余的可用内存空间。

13、堆栈溢出一般是由什么原因导致的?

  • 堆栈溢出一般包括堆内存溢出和栈内存溢出,两者都属于缓冲区溢出。
  • 堆内存溢出可能是堆的尺寸设置得过小/动态申请的内存没有释放。

14、编译和链接有什么不同?(如对外部符号的处理)

  • 编译(+汇编)生成的是目标文件(*.o)。编译过程中对于外部符号(如用extern跨文件引用的全局变量)不做任何解释和处理,外部符号对应的就是“符号”。
  • 链接生成的是可执行程序。链接将会解释和处理外部符号,外部符号对应的是地址。

15、一个32位的指针,如何按8字节的整数倍向下对齐,请写出代码。

答:设该指针为p,则代码为:

p &= ~(8 -1); 
  • 在存储的时候,为了提高效率,一般都会让地址偏移量落在2的m次方的位置上,而且经常有向上取整和向下取整两种需求。
  • 向下取整:
    #define  PALIGN_DOWN(x, align)  ((x) & ~((align) -1))  
    
  • 向上取整:
    #define  PALIGN_UP(x, align)  ((x) + ((align) - 1)) & ~((align) - 1)  
    

16、gcc 优化代码执行速度的编译选项是?

在这里插入图片描述

17、new 和 malloc 有什么区别?

  • new 与 delete 是 C++ 的操作符;而 malloc 与 free 是 C/C++ 的标准库函数。
  • C++ 允许重载 new/delete 操作符;而不允许重载 malloc/free。
  • new 返回的是对象类型的指针,严格与对象匹配;而 malloc 返回的是 void* 类型的指针,需要进行强制类型转换。
  • new 可以自动计算所申请内存的大小;而 malloc 需要显式指出所需内存的大小。
  • new 操作符从自由存储区上动态分配内存;而 malloc 函数从堆上动态分配内存。
  • new 内存分配失败会抛出 bac_alloc 异常;malloc 内存分配失败会返回 NULL。
  • new/delete 会调用对象的构造函数/析构函数,以完成对象的构造/析构;而 malloc 不会。

18、指针与引用的区别?

  • 指针是变量,存储的是地址;而引用跟原变量是同一个东西,是原变量的别名。
  • 指针有 const;而引用没有。
  • 指针的值可以为 NULL;而引用不行。
  • 非 const 指针可以改变;引用只能在定义时被初始化,之后不可改变。
  • 指针可以有多级,如二重指针;而引用只能有一级。
  • 指针和引用自增(++)的意义不一样,指针自增是地址增加,引用自增是原变量增加。
  • sizeof指针和引用得到的大小不一样,sizeof(指针)得到的是指针本身的大小,sizeof(引用)得到的是原变量的大小。

我的qq:2442391036,欢迎交流!


  • 13
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

须尽欢~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值