目录
变量的本质
数据类型和类型转换
有符号数和无符号数、数据溢出
定义和声明的区别
程序、程序文件模块和函数之间的关系
局部变量、全局变量、外部变量、静态变量、作用域、生命周期
内存中的段(section)
变量的本质
这里要说的变量既有普通变量,也有指针变量(被我们常常简称为指针)。
先说普通变量:
int a = 10;
如定义一个整型的变量a,初始值为10。
这句话给出了三个信息,分别是变量类型、变量名和变量值,这三个信息分别和存储大小、存储地址和存储内容一一对应。
变量名的本质就是内存空间的别名,通过变量名可以直接对这段内存进行读写。
定义变量的目的就是方便对存储在内存中的数据进行读写,不是直接通过地址,而是通过变量名来访问内存。
编译器根据我们定义的变量类型,会在内存中分配合适大小的存储空间和地址对齐。
再说指针变量:
指针是指针变量的简称,本质上也是一个变量,和普通变量并无本质区别,只不过指针变量中存储的是地址。
那既然是变量,就有对应的变量类型。
除了普通指针外,指针变量还有函数指针(变量)、数组指针(变量)。
普通指针、函数指针和数组指针之间并没有本质区别,区别在于指针指向的东西是什么。
那既然所有的指针变量类型本质都是一样的,那为什么在C语言中还要去区分它们?主要原因是不同的写法可以给编译器提供不同的消息。
写代码的时候把自己当成编译器。
复杂表达式的解析方法:第1步找核心,第2步找结合(需要知道符号的优先级,其中() > [] > * )
变量 | 变量类型 | 对应的存储大小 | |
普通变量 | int a | int | 不同平台int所占字节数不同 |
(普通)指针变量 | int *p | int * | 对32位系统,指针总是占4个字节;64位系统是8字节 |
函数指针(变量) | void (*pFunc)(void) | void (*) () | |
数组指针(变量) | int (*p)[5] | int (*p)[ ] |
为什么需要指针?
内存一般可分为静态内存和动态内存,一个程序被加载到内存运行时,代码段和数据段就属于静态内存,而堆栈则属于动态内存。
静态内存的特点:内存中各个变量的地址在编译期间就确定了,在程序运行期间不再改变,因此可以通过变量名直接访问。
动态内存的特点:变量的地址在程序运行期间是不固定的,如函数的局部变量。
栈:在函数调用过程中,分配的栈帧空间地址不同,但局部变量在函数栈帧内相对于栈帧指针的相对偏移不会改变; 堆:不仅动态变化,而且还是匿名访问,无法借助变量名或栈指针来访问,只能使用指针来间接访问 。
指针设计的初衷就是访问一片匿名的动态内存。
指针难以理解和掌握,为什么在编程中还要使用指针呢?
- 原因1:有些地方不得不使用指针,没有别的备选方案。
如动态堆内存的匿名访问
- 原因2:使用指针更容易编写出高质量高效率的程序。
如参数传递、函数的返回值。
- 原因3:一些链表、树等动态数据结构的实现也离不开指针,还有一些字符串指针、函数指针等
指针和函数传参(指针的常用技巧)
普通传参、传递地址、传递数组、传递结构体
“地址值传递”和“普通值传递”都是值传递,只是传递指针时,被传值比较特殊,是一个地址,具有指向某个空间的作用。
为了提高数组的传递效率,传递的都是数组首元素首字节地址,只有4个字节大小,效率非常高。
传递数组时,形参的写法有两种,一种是直接写成指针变量的形式,另一种是“类型 名称[]”,后一种写法是为了提高可辨识性。
结构体传递有4中传参方式:
- 成员值传递:传递结构体成员值,就是普通值传递
- 成员地址传递:传递结构体成员的地址,其实就是地址传递
- 传递整个结构体
- 结构体的地址传递:提高传参效率
数据类型分为基本数据类型和构造数据类型。
基本数据类型有:short, int, long, float, double, char;构造数据类型有:struct, union, enum。
类型对编译器来说,主要就是用于说明数据存储空间的大小以及数据的存储结构。
当处理器对两个数进行算术运算时,一般要求两个数的类型、大小、存储方式都相同,这是由CPU的硬件电路特性决定的。
类型转换分为隐式转换和显式转换,需要注意的是对普通变量还是指针变量。 对于指针变量的类型转换,一律要求显式强制类型转换,主要设计对
一个存储在物理内存中的数据,可以被看做一个有符号数,也可以被看做一个无符号数,关键看如何去解析它,它在内存里就是一串二进制数据0和1。
原码、反码和补码:
对于无符号数,原码、反码和补码是一样的;对于有符号数,采用补码形式存储,补码等于反码加1。
每一种数据类型都有它能表示的数值范围。
如signed char表示[128,127],unsigned char表示[0,255]。
一般来说,无符号数溢出会进行取模运算,继续周期轮回,使程序陷入死循环。
#include
int main()
{
unsigned char c = 255;
printf("c = %u\n", c);
c++;
printf("c = %u\n", c);
return 0;
}
如何防止数据溢出?
定义和声明的区别有符号数:两个整数相加小于0或两个负数相加大于0,说明溢出了;
无符号数:两个数的和小于其中任何一个加数,说明溢出了。
区别一:作用阶段不同:编写的代码需要经过预处理、编译、汇编、链接等步骤得到可执行文件,声明作用于编译阶段、定义作用在链接阶段;
区别二:定义时分配存储单元、声明时不分配存储单元。
- 局部变量:定义在函数内部的变量,形参是局部变量。
- 全局变量:定义在函数外不属于任何函数的变量,作用范围是从定义开始,到程序所在文件结束,只在某一个程序文件模块中有效。
- 外部变量extern:当程序包含多个程序文件模块时,通过外部变量声明可以将全局变量的作用范围扩展到其他文件模块。
- 静态全局变量static:将变量的作用范围仅限于当前的文件模块中,即使其他文件模块使用外部变量声明也不能使用该变量。
- 静态局部变量:延长了生命周期(但也只是限于本文件模块),并没有改变作用范围(只在函数内部)。
作用范围和声生命周期是两个完全的概念。
- 代码段(.text):函数
- 数据段(.data):初始化的全局变量、静态局部变量
- .bss段 :未初始化的全局变量
- .rodata段:字符串、字符串常量
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)