文章目录
- 前言
- 一、内存管理&编程题
- 1、由gcc编译的C语言程序占用的内存分为哪几个部分?
- 2、大小端
- 3、全局变量和局部变量的区别?
- 4、以下程序中,主函数能否成功申请到内存空间?
- 5、请问运行下面的 Test() 函数会有什么样的后果?
- 6、 请问运行下面的 Test() 函数会有什么样的后果?
- 7、请问运行下面的 Test() 函数会有什么样的后果?
- 8、在C语言中 memcpy 和 memmove 是一样的吗?
- 9、malloc 的底层是如何实现的?
- 10、在1G内存的计算机中能否通过malloc申请大于1G的内存?为什么?
- 11、内存泄漏是什么?
- 12、内存溢出是什么?与内存泄漏有何关系?
- 13、堆栈溢出一般是由什么原因导致的?
- 14、编译和链接有什么不同?(如对外部符号的处理)
- 15、一个32位的指针,如何按8字节的整数倍向下对齐,请写出代码。
- 16、gcc 优化代码执行速度的编译选项是?
- 17、new 和 malloc 有什么区别?
- 18、指针与引用的区别?
前言
记录一些招聘公司在招聘嵌入式软件岗位时的一些问题,此文为第四篇。
一、内存管理&编程题
1、由gcc编译的C语言程序占用的内存分为哪几个部分?
2、大小端
小端:一个数据的低位字节数据存储在低地址
大端:一个数据的高位字节数据存储在低地址
例如:int a=0x12345678; //a首地址为0x200,大端存储格式如下:
数据 | 0x12 | 0x34 | 0x56 | 0x78 |
---|---|---|---|---|
地址 | 0x200 | 0x201 | 0x202 | 0x203 |
如何判读一个系统的大小端存储模式?
- 方法一: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,欢迎交流!