一个C/C++编译的程序所占用的系统内存一般分为以下几个部分:
1)由符号起始的区块(BBS, Block Started by Symbol)段:存放程序中未初始化的全局数据和静态数据的一块内存区域。BSS段属于静态内存分配,程序结束后静态变量资源由系统自动释放。
2)数据段:存放程序中已初始化的全局变量和静态数据。数据段也属于静态内存分配。
3)代码段:也叫文本段,存放程序中的执行代码。
4)堆(heap):用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。它与数据结构中的堆是两回事,分配方式类似于链表。
5)栈(stack):存放程序临时创建的局部变量,一般包括函数括弧{}中定义的变量,但不包括static 声明的变量。
堆是动态分配内存的,并且可以分配使用很大的内存,使用不好会产生内存泄露。频繁地使用malloc 和free 会产生内存碎片(类似磁盘碎片)
所谓内存泄露是指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。
一般常说的内存泄露是指堆内存的泄露,内存泄露其实并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。
栈顶的地址和栈的最大容量是系统预先规定好的,在Windows下,栈的大小是2MB。而申请堆空间的大小一般小于2GB。
Linux 默认栈空间大小是8MB,通过命令ulimit -s 来设置。
缓冲区是程序运行的时候机器中的一个连续块,它保存了给定类型的数据,随着动态分配变量会出现问题。
缓冲区溢出是指当向缓冲区内填充数据位数超过了缓冲区自身的容量限制时,发生的溢出的数据覆盖在合法数据(数据、下一条指令的指针、函数返回值地址等)上的情况。
后果:1)引起程序运行失败,严重的导致系统崩溃。2)利用这种漏洞执行任意指令,危害系统安全。
是数据类型的关键字,而非函数。
strlen 执行的是一个计数器的工作,它从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描,直到碰到第一个字符串结束符’\0’为止,然后返回计数器的值。
sizeof 是C语言的关键字,它以字节的形式给出了其操作数的存储大小,操作数可以是个表达式或括在括号内的类型名,操作数的存储大小由操作数的类型决定。
两者的主要区别:
1)sizeof 是关键字,而strlen 是函数。sizeof 后如果是类型必须加括弧,如果是变量的话可以不加。
2)sizeof 操作符的结果类型是size_t,它的头文件中typedef 为 unsigned int 类型。该类型保证能够容纳实现所建立的最大对象的字节大小。
3)sizeof 可以用类型作为参数,strlen 只能用 char* 作参数,而且必须是以”\0”结尾的。sizeof还可以以函数作为参数,如 int g() ,则sizeof(g())的值等于sizeof(int)的值,在32位计算机下,该值为4.
4)当数组名做sizeof 的参数时不退化,传递给strlen 退化为指针。
以数组char a[10] 为例,在32位机器下,sizeof (a) = 1*10=10,而传递给strlen 就不一样了。
5)大部分编译程序的sizeof 都是在编译的时候计算的,所以可以通过sizeof(x)来定义数组维数。而strlen 计算则是在运行期计算的,用来计算字符串的实际长度,不是类型占内存的大小。
6)数组作为参数传给函数时传的是指针而不是数组,传递的是数组的首地址。
内存对齐导致。
struct是一种复合数据类型,其构成元素既可以是基本数据类型,也可是复合数据类型。
一般而言,struct 的sizeof 是所有成员对齐后长度相加,而union 的sizeof 是取最大的成员长度。
a、可以动态分配内存。
b、进行多个相似变量的一般访问。
c、为动态数据结构,尤其是树和链表,提供支持。
d、遍历数组,如解析字符串。
e、高效的按引用“复制”数组与结构,特别是作为函数参数的时候,可以按照引用传递函数参数,提高开发效率。
a、非空区别。在任何情况下都不能使用指向空值的引用。
b、合法性区别。在使用引用之前不需要测试它的合法性。相反,指针则应该总是被测试,防止其为空。
c、可修改区别。指针可以被重新赋值以指向另一个不同对象。但引用则总是指向在初始化时被指定的对象,以后也不能改变,但是指定的内容可以改变。
d、应用区别。总的来说,在以下情况应该使用指针:一是考虑到存在不指向任何对象的可能;二是需要能够在不同的时刻指向不同对象。
指针可以随时指向任何类型的内存块,而数组可以在静态存储区被创建。
不同:修改内容不同;所占字节不同。
不可以。对于指针而言,只能进行== 和 != 运算。
1、野指针是指指向不可用内存的指针。
造成野指针的3个原因:
1)任何指针变量在被创建时,不会自动成为NULL指针(空指针),其默认值是随机的,所以指针变量在创建的同时应当被初始化,或者将指针设为NULL,或者让它指向合法的内存,而不应该放之不理,否则就会成为野指针。
2)同时,指针被释放(free或delete)后,未能将其设置为NULL,也会导致该指针变为野指针。
3)指针操作超越了变量的作用范围。
2、空指针是一个特殊的指针,也是唯一一个对任何指针类型都合法的指针。
如果一个项目中存在两个C文件,而这两个C文件都include了同一个头文件,当编译时,这两个C文件要一同编译成一个可运行文件,可能会产生大量的声明冲突。而解决的办法是把头文件的内容都放在#ifndef 和#endif 中。
#include <filename.h> 和 #include "filename.h"有什么区别
前者用于标准库头文件,后者用于自定义头文件。对于#include <filename.h>
,编译器先从标准库路径开始搜索filename.h, 使得系统文件调用较快。而对于 #include “filename.h”,编译器先从用户的工作路径开始搜索,然后去寻找系统路径,使得自定义文件较快。
由于宏定义在预处理阶段进行,主要做的是字符替换工作,存在的缺陷:
a、它无法进行类型检查
b、由于优先级不同,使用宏定义时,可能会存在副作用。如#define ADD(a,b) a+b 就会存在问题,需要加括号。
c、无法单步调试。
d、会导致代码膨胀
e、在C++中,使用宏无法操作类的私有数据成员。
含参数的宏有时完成的是函数实现的功能,但是并非所有的函数都可以被含参数的宏所替代。
两者的特点:
1)函数调用时,首先求出实参表达式的值,然后代入形参。而使用带参的宏只是进行简单的字符替换。
2)函数调用是在程序运行时处理的,它需要分配临时的内存单元;而宏展开则是在编译时进行的,在展开时并不分配内存单元,也不进行值的传递处理,也没有“返回值”的概念。
3)对函数中的实参和形参都要定义类型。两者的类型要求一致,如果不一致,应进行类型转换;而宏不存在类型问题,宏名无类型,它的参数也无类型,只是一个符号代表,展开时代入指定的字符即可。宏定义时,字符串可以是任何类型的数据。
4)调用函数只可得到一个返回值,而用宏可以设法得到几个结果。
5)使用宏次数多时,宏展开后源程序会变得很长,因为每展开一次都使得程序内容增长,而函数调用不使源程序变长。
6)宏替换不占用运行时间,而函数调用则占运行时间。
7)参数每次用于宏定义时,它们都将重新求值,由于多次求值,具有副作用的参数可能会产生不可预料的结果。而函数调用则不会。
一般来说,用宏来代替简短的表达式比较合适。
有三种方法:
1)采用取反操作。如果是有符号数,取反之后,开头的0变为1,开头的1变为0,正负号发生变化(开头为1时即表示该数为负数);如果是无符号数则不会受此影响。
2)无符号数和有符号数相减的结果为无符号数。
3)通过改变符号位判断。
采用地址值相减的方法。
区别很小。
在C语言中,枚举为整型,枚举常量为 int 型。
具体区别:
a、枚举常量是实体中的一种,而宏定义不是实体
b、枚举常量属于常量,但宏定义不是常量。
c、枚举常量具有类型,但宏定义没有类型,枚举变量具有普通变量相同的性质,但宏没有。
d、#define 宏常量是在预编译阶段进行简单的值替换,枚举常量则是在编译的时候确定其值。
e、一般在编译器里可以调试枚举变量,但宏常量不行。
f、枚举可以一次定义大量相关的常量,而#define 宏一次只能定义一个。
1)原理不同。#define 是预处理指令,不做正确性检查;typedef 是关键字,有类型检查功能。
2)功能不同。typedef 用来定义类型的别名,这些类型不只包括内部类型,还包括自定义类型,使类型易于记忆。而#define 不只是取别名,还可以定义常量、变量、编译开关等。
3)作用域不同。#define 没有作用域限制;而typedef 有自己的作用域。
4)对指针的操作不同。修饰指针类型时,作用不同,一个可以保持一致性,一个不可以保持一致性。
宏代码本身不是函数,但使用起来却像函数,预处理器用复制宏代码的方式代替函数调用,省去了参数压栈、生成汇编语言的CALL调用,返回参数,执行return等过程,从而提高速度。
内联函数是代码被插入到调用者代码处的函数。内联函数也不是万能的,它的使用是有所限制的,它只适合函数体内简单的函数使用,不能包含复杂的结构和控制语句(如for while switch),并且内联函数本身不能直接调用递归函数(自己内部还调用自己的函数)。
两者的区别:
1)宏定义是在预处理阶段进行代码替换,而内联函数是在编译阶段插入代码;
2)宏定义没有类型检查,而内联函数有类型检查。
内联函数与普通函数的最大区别:在于其内部的实现方式上,普通函数在调用时有寻址过程,而内联函数不需要寻址。
效果虽然一样,但侧重点不同。
define 既可以替代常数值,又可以替代表达式,甚至是代码段,但容易出错;
const 的引入可以增强程序的可读性,使程序维护与调试更加方便。
主要区别:
1)define 只是用来进行单纯的文本替换,define的生命周期止于编译期,不分配内存空间,它存在于程序的代码段,在实际程序中,它只是一个常数,一个命令中的参数并没有实际存在;而const 常量存在于程序的数据段,并在堆栈中分配了空间,const 常量在程序中确确实实地存在,并且可以被调用和传递。
2)const 常量有数据类型,而 define 常量没有数据类型。
3)很多IDE支持调试const定义的常量,而不支持define定义的常量。