static定义的变量与全局变量都会被放在全局区里,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后有系统释放。(关于内存分配,还有栈区(stack),堆区(heap),文字常量区,程序代码区)
虽然静态变量和全局变量都会存储在全局区,但作用域是有区别的,在此加以区分。
https://www.cnblogs.com/gxcdream/p/4805612.html
源代码被编译之前,由预处理器(Preprocessor)对源代码进行的处理。这个过程并不对程序的源代码进行解析,但它把源代码分割或处理成为特定的符号用来支持宏调用
预处理器的主要作用就是把通过预处理的内建功能对一个资源进行等价替换,最常见的预处理有:文件包含,条件编译、布局控制和宏替换4种。
#include (<>用来引用标准库头文件,""常用来引用自定义的头文件。<>编译器只搜索包含标准库头文件的默认 目录,“”首先搜索正在编译的源文件所在的 目录,找不到时再搜索包含标准库头文件的默认 目录.)
#if/#elif/#else/#endif
#ifdef
#define/#undef (在编译前,define的常量会直接替换源代码中的符号。#define的缺点:1. 不支持类型检查;2. 不考虑作用域;3. 符号名不能限制在一个命名 空间中;)
定义宏
#define Print(Var) count<<(Var)<
#ifndef (防止重复引入某些头文件)
#line (重新定义当前行号或文件名)
#error(输出编译错误消息,停止编译)
标准的预处理器宏
LINE 当前源文件中的代码行号,十进制整数
FILE 源文件的名称,字符串字面量
DATE 源文件的处理日期,字符串字面量,格式mmm dd yyyy其中mmm是月份如Jan、Feb等 dd是01-31 yyyy是四位的年份
TIME 源文件的编译 时间,也是字符串字面量格式是hh:mm:ss
STDC 这取决于实现方式,如果编译器选项设置为编译标准的C代码,通常就定义它,否则就不定义它
__cplusplus 在编译C++ 程序时,它就定义为199711L
#pragma (设定编译器的状态或者是指示编译器完成一些特定的动作)
#pragma message("") 当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。
#pragma code_seg([“section-name”[,“section-class”]]) 它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它
#pragma once 只要在头文件的最开始加入这条指令就能够保证头文件被编译一次
#pragma hdrstop 表示预编译头文件到此为止,后面的头文件不进行预编译 (预编译头文件可以加快链接速度,但又会占用磁盘空间,所以使用这个选项排除一些头文件)
#pragma startup指定编译优先级
#pragma package(smart_init) ,BCB就会根据优先级的大小先后编译
#pragma resource ".dfm"表示把.dfm文件中的资源加入工程
#pragma warning(disable:450734;once:4385;error:164) //不显示4507和34号警告信息 //4385号警告信息仅报告一次 //把164号警告信息作为一个错误
#pragma warning(push)保存所有警告信息的现有的警告状态。
#pragma warning(push,n)保存所有警告信息的现有的警告状态,并且把全局警告等级设定为n
#pragma warning(pop)向栈中弹出最后一个警告信息
#program comment(comment-type,[“commentstring”])将一个注释记录放入一个对象文件或可执行文件。
#program data_seg用来建立一个新的数据段并定义共享数据 (A. #pragma data_seg()一般用于DLL中。也就是说,在DLL中定义一个共享的有名字的数据段。最关键的是:这个数据段中的全局变量可以被多个进程共享,否则多个进程之间无法共享DLL中的全局变量。B.共享数据必须初始化,否则微软编译器会把没有初始化的数据放到.BSS段中,从而导致多个进程之间的共享行为失败)
#program region用于折叠特定的代码段
#pragma disable 在函数前声明,只对一个函数有效。该函数调用过程中将不可被中断。一般在C51中使用较多
#program pack(n) 规定按n字节对齐
1.什么是对齐?为什么要对齐?
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈。
2.pragma pack语法
用sizeof运算符求算某结构体所占空间时,并不是简单地将结构体中所有元素各自占的空间相加,这里涉及到内存字节对齐的问题,有时候为了内存对齐需要补齐空字节。通常写程序的时候,不需要考虑对齐问题。编译器会替我们选择适合目标平台的对齐策略。当然,我们也可以通知给编译器传递预编译指令而改变对指定数据的对齐方法。
语法:#pragma pack( [show] | [push | pop] [, identifier], n )
show(optical):显示当前packing aligment的字节数,以warning message的形式显示。(可选参数;显示当前packing aligment的字节数,以warning message的形式被显示)
push(optical): (可选参数;将当前指定的packing alignment数值进行压栈操作,这里的栈是the internal compiler stack,同时设置当前的packing alignment为n;如果n没有指定,则将当前的packing alignment数值压栈;)
Pushes the current packing alignment value on the internal compiler stack, and sets the current packing alignment value to n. If n is not specified, the current packing alignment value is pushed.
pop(optical): (可选参数;从internal compiler stack中删除最顶端的record;如果没有指定n,则当前栈顶record即为新的packing alignment数值;如果指定了n,则n将成为新的packing aligment数值;如果指定了identifier,则internal compiler stack中的record都将被pop直到identifier被找到,然后pop出identitier,同时设置packing alignment数值为当前栈顶的record;如果指定的identifier并不存在于internal compiler stack,则pop操作被忽略;)
Removes the record from the top of the internal compiler stack. If n is not specified with pop, then the packing value associated with the resulting record on the top of the stack is the new packing alignment value. If n is specified, for example, #pragma pack(pop, 16), n becomes the new packing alignment value. If you pop with identifier, for example, #pragma pack(pop, r1), then all records on the stack are popped until the record that hasidentifier is found. That record is popped and the packing value associated with the resulting record on the top of is the stack the new packing alignment value. If you pop with an identifier that is not found in any record on the stack, then the pop is ignored.
identifier(optional): (可选参数;当同push一起使用时,赋予当前被压入栈中的record一个名称;当同pop一起使用时,从internal compiler stack中pop出所有的record直到identifier被pop出,如果identifier没有被找到,则忽略pop操作)
When used with push, assigns a name to the record on the internal compiler stack. When used with pop, pops records off the internal stack until identifieris removed; if identifier is not found on the internal stack, nothing is popped.
n (optional): (可选参数;指定packing的数值,以字节为单位;缺省数值是8,合法的数值分别是1、2、4、8、16)
Specifies the value, in bytes, to be used for packing. If the compiler option /Zp is not set for the module, the default value for n is 8. Valid values are 1, 2, 4, 8, and 16. The alignment of a member will be on a boundary that is either a multiple of n or a multiple of the size of the member, whichever is smaller.
3.结构体对齐规则
结构体中各个成员按照它们被声明的顺序在内存中顺序存储。
1)将结构体内所有数据成员的长度值相加,记为sum_a;
2)将各数据成员内存对齐,按各自对齐模数而填充的字节数累加到和sum_a上,记为sum_b。对齐模数是【该数据成员所占内存】与【#pragma pack指定的数值】中的较小者。
3)将和sum_b向结构体模数对齐,该模数是【#pragma pack指定的数值】、【未指定#pragma pack时,系统默认的对齐模数8字节】和【结构体内部最大的基本数据类型成员】长度中数值较小者。结构体的长度应该是该模数的整数倍。
3.1 基本数据类型所占内存大小
3.4关于类
空类是会占用内存空间的,而且大小是1,原因是C++要求每个实例在内存中都有独一无二的地址。
(一)类内部的成员变量:
• 普通的变量:是要占用内存的,但是要注意对齐原则(这点和struct类型很相似)。
• static修饰的静态变量:不占用内容,原因是编译器将其放在全局变量区。
(二)类内部的成员函数:
• 普通函数:不占用内存。
• 虚函数:要占用4个字节,用来指定虚函数的虚拟函数表的入口地址。所以一个类的虚函数所占用的地址是不变的,和虚函数的个数是没有关系的
#pragma pack(4)
class cBase{};
sizeof(cBase)=1
3.4.2 子类
子类所占内存大小是父类+自身成员变量的值。特别注意的是,子类与父类共享同一个虚函数指针,因此当子类新声明一个虚函数时,不必在对其保存虚函数表指针入口。
一个由C/C++编译的程序占用的内存分为以下几个部分
(1)栈区(stack)— 由编译器自动分配释放,存放函数的参数值,局部变量的值等。
(2)堆区(heap) — 由程序员分配和释放,若程序员不释放,程序结束时可能由OS回收。
(3)全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量
和静态变量在一块区域, 未初始化的全局变量、未初始化的静态变量在相邻的另一块区域。
(4)文字常量区 — 常量字符串就是放在这里的。
(5)程序代码区 — 存放函数体的二进制代码。
堆栈(stack)是内存中的一个连续的块。一个叫堆栈指针的寄存器(SP)指向堆栈的栈顶。堆栈的底部是一个固定地址。堆栈有一个特点就是,后进先出。也就是说,后放入的数据第一个取出。
堆(heap)是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。
1. 在高级语言中,程序函数调用、函数中定义的变量都用到栈(stack)
2. 用malloc, calloc, realloc等函数分配得到变空间是在堆(heap)上
3. 在所有函数体外定义的是全局量
4. 加了static修饰符后不管放在在哪里都属于静态变量,存放在全局区(静态区)
5. 在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用
6. 在函数体内定义的static表示只在该函数体内有效
7. 函数中的"armfly"这样的字符串存放在常量区
所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。 如果加了static,就会对其它源文件隐藏。存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它的使用范围
C++中的类成员声明static(有些地方与以上作用重叠)
静态成员函数不含this指针。
静态成员是可以独立访问的,也就是说,无须创建任何对象实例就可以访问。
类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致 了它仅能访问类的静态数据和静态成员函数。
局部变量:局部变量也只有局部作用域,它是自动对象(auto),它在程序运行期间不是一直存在,而是只在函数执行期间存在,函数的一次调用执行结束后,变量被撤销,其所占用的内存也被收回。
全局变量:全局变量具有全局作用域。全局变量只需在一个源文件中定义,就可以作用于所有的源文件。当然,其他不包含全局变量定义的源文件需要用extern关键字再次声明这个全局变量。
静态局部变量:静态局部变量具有局部作用域,它只被初始化一次,自从第一次被初始化直到程序运行结束都一直存在,它和全局变量的区别在于全局变量对所有的函数都是可见的,而静态局部变量只对定义自己的函数体始终可见。
静态全局变量:静态全局变量也具有全局作用域,它与全局变量的区别在于如果程序包含多个文件的话,它作用于定义它的文件里,不能作用到其它文件里,即被static关键字修饰过的变量具有文件作用域。这样即使两个不同的源文件都定义了相同名字的静态全局变量,它们也是不同的变量。
堆亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或delete释放内存。动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存。 但是,良好的编程习惯是:如果某动态内存不再使用,需要将其释放掉,否则,我们认为发生了内存泄漏现象。
栈在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
静态存储区内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量
此处只介绍几个基本的性质。
class A
{
public:
int& getValue() const
{
// a = 10; // 错误
return a;
}
private:
int a; // 非const成员变量
};
b. const修饰数据成员表示该数据成员一旦赋值就不可变,并且只能在类的构造函数初始化列表中赋值,不能在类构造函数体内赋值
class A
{
public:
A(int x) : a(x) // 正确
{
//a = x; // 错误
}
private:
const int a;
};
c. const修饰类对象,该对象内的任何成员变量都不能被修改,因此不能调用该对象的任何非const成员函数,因为对非const成员函数的调用会有修改成员变量的企图
class A
{
public:
void funcA() {}
void funcB() const {}
};
int main
{
const A a;
a.funcB(); // 可以
a.funcA(); // 错误
const A* b = new A();
b->funcB(); // 可以
b->funcA(); // 错误
}
d. const修饰形参,指不可改变形参。(function(const int &x),这里x是一个常量引用,如果没有const,那么在函数内对x的修改会反应到实参上,但由于有const,则表明x不能被修改。但与直接传值是不同的,传值的话会有两次copy,但通过这种方式只有一次copy)
6. const的常量值是可以被强制修改的。(const int i=0; int p = (int)&i; *p=100。通过另外一个指向同一地址的变量修改
下面介绍几个例子:
a. const int a 和int const a是一样的,a不能修改。
b. const int *b 和 int const *b是一样的,与放在int 左右没有关系,b不能修改。即值不能修改
c. int * const c,c不能修改。也就是指针不能修改,只能指向一个地址。
d. const int * const d, 则是d和d都不能修改
详细介绍请参考:
https://blog.csdn.net/Function_Dou/article/details/84987468
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面列举几个应用场景。
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
(1). 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)
(2). 对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的
1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量
2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集
3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多
Const作用:定义常量、修饰函数参数、修饰函数返回值三个作用。被Const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性
1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误
2) 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试
宏 | 函数 | |
---|---|---|
处理时间 | 编译时 | 程序运行时 |
参数类型 | 没有参数类型问题 | 定义实参、形参类型 |
处理过程 | 不分配内存 | 分配内存 |
程序长度 | 变长 | 不变 |
运行速度 | 不占运行时间 | 调用和返回占用时间 |