[C++常见面试笔试题汇总] 程序设计基础 - 内存分配sizeof指针篇
Posted linuxandmcu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[C++常见面试笔试题汇总] 程序设计基础 - 内存分配sizeof指针篇相关的知识,希望对你有一定的参考价值。
2 内存分配
系统蓝屏,很大原因都是系统自身代码有缺陷引起的,而系统代码缺陷很大程度上与内存分配不当有关。由于内存分配不当引起的堆栈溢出、缓冲区溢出等问题,常常会导致系统瘫痪甚至崩溃,所以理解内存分配对于一名合格的程序员而言非常有必要。
2.1 内存分配的形式有哪些?
一个C/C++编译的程序所占用的系统内存一般分为以下几个部分的内容:
(1) 由符号起始的区块(Block Started by Symbol, BSS)段:BSS段通常是指用来存放程序中未初始化的全局数据和静态数据的一块内存区域。BSS段属于静态内存分配,程序结束后静态变量资源由系统自动释放。
(2) 数据段(data segment):数据段通常是指用来存放程序中己初始化的全局变量的一块内存区域。数据段也属于静态内存分配。
(3) 代码段(code segmeni/text segment):代码段有时候也叫文本段,通常是指用来存放程序执行代码(包括类成员函数和全局函数以及其他函数代码)的一块内存区域,这部分区域的大小在程序运行前就已经确定,并且内存区域通常是只读。在代码段中,也有可能包含一些只读的常数变量,如字符串常量。
(4) 堆(heap):堆用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc或new等函数分配内存时,新分配的内存就被动态添加到 堆上(堆被扩张),当利用free或delete等函数释放内存时,被释放的内存从堆中被删除(堆被缩减)。堆一般由程序员分配释放,若程序员不释放,程序结束时可能由操作系统回收。需要注意的是,它与数据结构中的堆是两回事,分配方式类似于链表。
(5) 栈(stack):栈用户存放程序临时创建的局部变量,一般包括函数括弧中定义的变量。除此之外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且等到调用结束后,函数的返回值也会被存放回栈中。栈由编译器自动分配释放,存放函数的参数值、局部变量的值等。其操作方式类似于数据结构中的栈。栈内存分配运算内置于处理器的指令集中,一般使用寄存器来存取,效率很高,但是分配的内存容量有限。
需要注意的是,代码段和数据段之间有明确的分隔,但是数据段和堆栈段之间没有,而且栈是向下增长的,堆是向上增长的。程序示例如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int global=0; //全局初始化区(数据段)
char *pl; //全局未初始化区(BSS段)
int main()
{
int a; //栈
char s[]="abcdefg"; //栈
char *p2; //栈
char *p3="123456789"; //p3在栈上,"123456789"在常量区
static int c=0; //全局(静态)初始化区
pl=(char *)malloc(100); //分配而来的100B的区域就在堆中
strcpy(p1,"123456789") //"123456789"放在常量区,编译器可能会将它与p3所指向的
//"123456789"优化成一个地方
return 0;
}
2.2 什么是内存泄露?
堆是动态分配内存的,并且可以分配使用很大的内存,使用不好会产生内存泄露。频繁地使用malloc和free会产生内存碎片(类似磁盘碎片)。
所谓内存泄露(memory leak)是指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。一般常说的内存泄露是指堆内存的泄露,内存泄露其实并非指内存在物理上的消 失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
应用程序一般使用malloc、calloc、realloc、new等函数从堆中分配到一块内存,使用完后,程序必须负责相应地调用free或delete释放该内存块,否则这块内存就不能被再次使用, 造成内存泄露。
例如,对指针进行重新赋值,程序代码如下:
char *memoryArea = malloc(lO);
char *newArea = malloc(lO);
memoryArea = newArea;
对memoryArea的赋值会导致memoryArea之前指向的内容丢失,最终造成内存泄露。如
下程序就因为未能对返回值进行处理,最终导致内存泄露。
char *fun()
{
return malloc(10);
}
void callfun()
{
fun();
}
内存泄露往往会导致系统出现CPU资源耗尽的严重后果,所以开发人员在编码过程中要养成良好的编程习惯,用malloc或new分配的内存都应当在适当的时机用free或delete释 放,在对指针赋值前,要确保没有内存位置会变为孤立的,另外要正确处理使用动态分配的函数返回值。
2.3 栈空间的最大值是多少?
在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。栈顶的地址和栈的最大容量是系统预先规定好的,在Windows下,栈的大小是2MB。而申请堆空间的大 小一般小于2GB。
由于内存的读取速度比硬盘快,当程序遇到大规模数据的频繁存取时,开辟内存空间很有作用。栈的速度快,但是空间小,不灵活。堆是向高地址扩展的数据结构,是不连续的内存区域。 这是由于系统是用链表来存储空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址的,而堆的大小受限于计算机系统中有效的虚拟内存,所以堆获得的空间比较灵活,也比较大,但是速度相对慢一些。
一般情况下,可以通过以下两种方法更改栈的大小。
(1) link时用/STACK指定它的大小,或者在.def中使用STACKSIZE指定它的大小。
(2) 使用控制台命令“EDITBIN”更改exe的栈空间大小。
需要注意的是,Linux默认栈空间大小为8MB,通过命令ulimit-s来设置。
2.4 什么是缓冲区溢出?
缓冲区是程序运行的时候机器内存中的一个连续块,它保存了给定类型的数据,随着动态分配变量会出现问题。缓冲区溢出是指当向缓冲区内填充数据位数超过了缓冲区自身的容量限制时,发生的溢出的数据覆盖在合法数据(数据、下一条指令的指针、函数返回地址等)上的情况。最好的情况是程序不允许输入超过缓冲区长度的字符并检查数据长度,由于大多数程序 都会假设数据长度总是与所分配的储存空间相当,进而存在缓冲区溢出安全隐患。
程序示例如下:
#include <unistd.h>
void Test()
{
char buff[4];
printf("Some input:");
gets(buff);
puts(buff);
}
该程序的Test()函数中使用了标准的C语言输入函 数getc(),由于它没有执行边界检查,最终会导致 Test()函数存在缓冲区溢出安全漏洞。Test()函数的缓冲区最多只能容纳3个字符和一个空字符,所以超过4个字符就会造成缓冲区溢出。
人为的缓冲区溢出一般是由于攻击者写一个超过缓冲区长度的字符串植入到缓冲区,然后再向一个有限空间的缓冲区中植入超长的字符串,这时可能会出现两个结果:一是过长的字符串覆盖了相邻的存储单元,引起程序运行失败,严重的可导致系统崩溃;另一个结果就是利用这种漏洞可以执行任意指令,甚至可以取得系统root特级权限,进而危害系统安全。
3 sizeof
对于变量而言,sizeof的大小就像变量的体积一样,它的大小直接影响着变量的存储与访问效率。
3.1 sizeof是关键字吗?
sizeof是关键字,而非函数,很多时候它都可能被误解是操作符,这是不对的。
引申:预处理指令是否是C语言中的语言类型?
不是。语句是编程语言的基础,C语言中的语言类型一共有以下5种:
(1) 表达式语句。
(2) 函数调用语句。
(3) 控制语句。if语句、switch语句(这两种为条件判断语句)、do while语句、while语 句、for语句(这3种为循环执行语句)、break语句、continue语句、return语句、goto语句 (这4种为转向语句)。
(4) 复合语句。
(5) 空语句。
需要注意的是,由于预处理指令的结尾不能添加分号,所以预处理指令不是语句。