一 C语言中的变量存储类别:
- 1 auto(自动)
- 2 register(寄存器)
- 3 static (静态)
- extern (外部)
其中, auto 和 register 变量属于自动分配方式,而 static 和 extern 变量属于静态分配方式。不同的分配方式下,变量的生存期,作用域和可见域各不相同。
自动变量 auto:
-
C语言默认所定义的变量是auto变量。
-
auto变量的作用域和生存期都局限在定义它的代码块中,所谓代码块,是指用两个花括号包裹起来的代码行,函数只是代码块的一种,常见的代码块还有if结构、for结构等等,哪怕只是两个成对花括号,也能构成一个独立代码块。此外,结合“先声明,后使用”的原则,可知,auto变量的作用域和生存期对应着从定义到所在代码块结束这块时空区域。
-
自动变量 包括 :结果体,公用体,结果体数组,公用体数组,数组和指针。
-
auto自动变量特点:只有在定义他们的时候才创建,在定义他们的函数返回时系统回收变量所占的存储空间对这些变量存储空间的分配和回收是由系统自动完成的,一般情况下,不做专门说明的局部变量,块语句中的变量,函数的形式参数,都是自动变量
auto变量没有初始化,会得到不确定的值,为了防止得到不确定的值,所以变量必须初始化。
// auto.c 自动变量
#include<stdlib.h>
#include<stdio.h>
int DATA = 100; //全局变量,不是自动变量
void main()
{
int num = 8; //自动变量
int arr[5] = { 4,5,6,7,8 }; //自动变量
int* p=#//自动变量
getchar();
}
寄存器变量 Register:
寄存器变量是存储在 CPU 的寄存器中,或者说存储在最快的可用内存中。与普通变量相比,访问和处理这些变量速度更快。由于寄存器变量存储在寄存器而非内存中,所以无法获取寄存器变量的地址,在绝大多数方面,寄存器变量和自动变量都是一样,也就是说,它们都是块作用域,无连接和自动存储期。
寄存器变量与机器硬件密切相关,不同类型的计算机,寄存器的数目不一样,通常为2 ~ 3个,对于在同一函数中说明的多余2~3个的寄存器变量,C编译程序会自动将寄存器变量变为自动变量
寄存器说明符只能用于说明函数中的变量和函数中的形参,因为不允许将全局变量或者静态变量说明为 register 。
void main()
{
int n = 100;
_asm //汇编
{
mov eax ,n //eax 是一个寄存器,n 的值传给 eax
add eax, 200 // eax 的值增加 200
mov n,eax // 将 eax 的值赋值给 n
}
printf("n=%d \n", n);
int n1= getchar();
}
监控寄存器的值
示例:寄存器变量快速计算
#include<stdlib.h>
#include<stdio.h>
#include<time.h>
#include<Windows.h>
void main()
{
time_t start, end; //代表时间开始和结束
time(&start); //获取当前时间,放在声明的变量中
//休眠3秒
//Sleep(3000);
register double res = 0.0; // vs 编译器会自动优化 频繁使用的变量,这里加不加 register 执行效果都一样
for (register int i = 0; i < 1000000000; i++)
{
res += i;
}
printf("res=%d \n", res);
time(&end);
printf("时间差= %d \n", (unsigned int)(end - start));
getchar();
}
全局变量 extern:
- 全局变量:extern变量又称全局变量,放在静态存储区,所谓全局,是说该变量可以在程序的任意位置使用,其作用域是整个程序代码范围内,可以被程序其他函数所引用,不仅仅局限在本文件
- 创建一个全局变量并赋值就是定义,没有赋值就是声明。
- 对于extern全局变量来说,有声明和定义之分,声明可以有多个,但是定义只能有一个。
- 全局变量的声明与定义在函数外部:Int num 就会被解析为extern int num; Int num = 10就会被解析为 extern int num =10。
- 全局变量定义的基本格式为:extern 类型 变量名 = 初始化表达式 ; 此指令通知编译器在静态存储区中开辟一块指定类型大小的内存区域,用于存储该变量。 例如:创建了一个初始值为100的int型全局变量m:extern int m=100;
- C语言规定,只要是在外部,即不是在任何一个函数内定义的变量,编译器就将其当作全局变量,无论变量定义前是否有extern说明符
- 全局变量仅有声明,没有赋值,调用时会报错。
- 当全局变量定义时,当且仅当省略了extern时,初始化表达式才可省略,系统默认将其初始化为0
- 对于定义的全局数组或结构,编译器将其中的每个元素或成员的所有位都初始化为0
- 定义与声明的区别:把建立存储空间的变量声明称定义,而把不需要建立存储空间的声明称为声明
- 全局声明:在main中调用的函数如果定义在main函数之后,要成功运行就必须在main函数之前加上这个函数的声明
- 在main函数中调用的全局变量如果是在main函数的下方,就必须在main函数调用这个变量之前加上它的声明
- 跨文件使用全局变量,一定要声明,extern可以省略,一维编译器会自动添加extern
- 全局变量的作用域:全局变量的作用域是整个程序,不论该程序由几个文件组成,理论上,可以在程序的任意位置使用定义的全局变量,但在特定位置处,全局变量是否可见取决于是否对其进行了合理声明。不进行任何声明时,全局变量的可见域为从定义到本文件结束。
- 函数中的局部变量如果与全局变量同名,局部变量会屏蔽全局变量。
全局变量的利弊:
- 为函数间数据传递提供了新的途径,函数返回值仅仅只能有1个,很多情况下,这不能满足要求,而全局变量可用于更多处理结果
- 利用全局变量可以减少形参和实参的个数,省去函数调用时的时空开销,提高程序运行的效率
- 全局变量在程序执行期间都有效,一直占据着存储单元,不像局部变量等在调用执行期间临时占用内存,退出函数时便将其释放。最大的问题是降低了函数的封装性和通用性,由于函数中存在全局变量,因此,如果想把函数复用在其他文件中,必须连所涉及的全局变量一块移植过去,容易引发各种问题,造成程序不可靠。全局变量使得函数间独立性下降,耦合度上升,可移植性和可靠性变差。
- 为了代码的可移植,在可以不使用全局变量的情况下应尽量避免使用全局变量。
静态变量 static:
static变量的定义格式为:static数据类型 变量1[=初始化表达式], 变量2[=初始化表达式]……; 与extern变量都是全局变量不同,static变量有静态全局变量和静态局部变量之分。
static局部变量应用场合: 对局部变量用static声明,把它分配在静态存储区,该变量在整个程序执行期间不释放,其所分配的空间始终存在。常使用静态局部变量在函数调用间歇保存某些变量的值。
局部变量:每次块语句结束,就自动回收,重新分配,静态局部变量:始终在内存,一旦定义初始化,即使再次执行初始化的语句,也仅仅执行一次初始化,生命周期很长,与程序共存亡。
使用静态局部变量,可以代码的移植性更强
静态局部变量,只有定义,没有声明。如果定义时没有赋值,默认其值为0;
全局变量和静态全局变量的区别:
-
extern作用域是本程序的所有源代码文件,只要在一个文件中定义,在其他文件中使用时只要对其进行声明即可。
-
静态全局变量其作用域仅限于从定义位置起到本文件结束的一段代码区域,不能被其他文件中的函数所使用。
静态全局变量的优点:静态全局变量实际上是对extern变量破坏封装性和可靠性的一种改良,便于代码的移植,代码的独立性可靠性得到了保障,可以实现封装。
静态全局变量,如果没有赋值,又恰好使用,编译器会把声明当作定义,并自动复制为0;如果出现定义,就以定义的数值为准。
如果声明的是静态全局变量数组,并且没有对其元素赋值,编译器会将其中的元素赋值为0;如果声明的是静态全局结构体,没有赋值的情况下编译器也会自动赋值为0;
总结:
- 静态局部变量,作用域局限于当前的块语句,除了生存期是整个程序执行期间(与程序共存亡)外,其作用域与可见域与普通auto变量完全一样。 Static局部变量只有定义,没有声明。静态局部变量分配在静态存储区,该变量在整个程序执行期间不释放,其所分配的空间始终存在。常使用静态局部变量在函数调用间歇保存某些变量的值。
静态全局变量:静态全局变量其作用域仅限于从定义位置起到本文件结束的一段代码区域,不能被其他文件中的函数所使用
二 作用域区分:
变量可分为局部变量和全局变量。
-
局部变量: 在函数内部定义的变量,局部变量仅在定义它的函数内部才有效使用,其作用域仅限在函数内,即从变量定义开始到函数体结束。通常编译器不会为局部变量分配内存单元,而是在程序运行中,当局部变量所在的函数被调用时,系统根据需要临时为其分配内存。当函数执行结束时,局部变量被撤销,占用的内存被回收。
-
全局变量:在函数外部定义的变量称为全局变量,也称外部变量,全局变量的作用域比较广泛,全局变量不属于任何一个函数,理论上可以被其作用域中的所有函数访问,因此,提供了一个不同函数之间联系的途径,使函数之间的数据联系不知局限于参数的传递和 return 语句。全局变量一经定义,编译器就会为其分配固定的内存单元,在程序运行期间,这块内存单元始终有效,一致到程序执行完毕才有操作系统收回该内存块。
示例:局部变量作用域
#include<stdio.h>
#include<stdlib.h>
void go(int num )
{
int a = 100;
num = 10;
}
void main()
{
printf("%d\n", a);
printf("%d\n", num);
system("pause");
}
现在编译 结果如下:
#include<stdio.h>
#include<stdlib.h>
void go(int num )
{
//printf("%d\n", a); 这里局部变量 a 也不能访问 ,在 c 语言中 是从上往下编译的 ,前面没有定义,后面就不能访问。
int a = 100;
num = 10;
printf("%d\n", a);
printf("%d\n", num);
}
void main()
{
//printf("%d\n", a); 变量 a 在 go 函数中定义,其作用范围 是从 变量定义到 go 函数体结束的 的大括号 '}'。
//printf("%d\n", num); num 也只能在 go 函数的开始和结束范围来访问。
go(4);
system("pause");
}
示例 :局部变量的内存分配
#include<stdio.h>
#include<stdlib.h>
void showAddress(int n)
{
int num = 10;
n = 5;
printf("%x , %x \n", &num, &n); //输出变量的内存地址
} //在这里插入断点,观测内存数据
void main()
{
showAddress(2);
printf("\n\n"); //在这里插入断点,观测内存数据
system("pause");
}
当断点停在 showAddress 函数 的 结束符号 } 之前 ,对应内存中的数值与程序中给变量赋值一致,此时变量内存还没有被回收。
继续执行,到下一个断点处。
当函数执行完成后内存被回收,被其他程序使用。也有可能没有立即回收,如上面 内存地址 57f6b4 的内存。
局部变量,在函数调用的时候,才分配内存空间,在函数执行完成之后就回收内存,用作他用。
在函数内定义的变量,其作用范围局限于此函数的内部,这种表变量叫局部表变量,函数的形参,在 main 函数中定义的变量也是局部变量。
局部变量在调用时由系统分配存储区,在不同的函数中同名的变量实际上在内存中占不同的单元,因此,在不同的函数中可以定义相同名字的局部变量。
示例:局部变量的作用域1
// 不同的块语句,可以定义相同的局部表变量,编译器会自动分配不同的内存地址。
// 但是相同的块语句,如果定义相同的变量,就是非法的,就会报重定义错误。
void main()
{
int num = 100;
//int num = 10; //重定义错误
printf("num=%d, num 地址 = %p\n", num, &num);
{
int num = 11;
printf("num=%d, num 地址 = %p \n", num, &num);
}
system("pause");
示例:全局变量
#include<stdio.h>
#include<stdlib.h>
/*
全局变量,不属于任何一个函数,在作用域范围内,任何一个函数都可以访问全局变量。
全局变量可以进行函数之间的通信,不用参数与 return 实现。
*/
int DATA = 10; //全局变量声明, 根据软件工程规范,大写为全局变量,小写为局部变量
void jian()
{
DATA -= 3;
}
void jia()
{
DATA += 3;
}
void main()
{
jian();
printf("全局变量=%d , 地址=%p\n", DATA,&DATA);
jia();
printf("全局变量=%d , 地址=%p\n", DATA, &DATA);
system("pause");
}
从上面例子可以看出,全局变量的内存始终存在,直到程序结束才被计算机回收。
给全局变量分配内存优先于 main 函数
在函数括号外定义的变量就叫全局变量。其作用范围从定义处一致到文件的末尾。由于全局变量的作用域很大,若在某个函数中修改了全局变量的值,在其他函数中读取的就是这个新值。
建议用大写命名全局变量,便于区分。
三 生存期,作用域,可见域:
生存期
生存期指的是在程序运行过程中,变量从创建到撤销的一段时间
生存期的长短取决于变量的存储方式,对于自动分配(栈分配),变量与其所在的代码块共存亡;对于静态分配(编译器预分配),变量与程序共存亡,程序开始执行即存在,一直到程序执行结束后才回收;对于动态存储的内存块,有程序员手动分配和手动释放。
对于程序代码区中的函数,常量区的字符串常量和其他常量等,结果体和公用体的定义,它们的生存期都是与程序共存亡。
示例:局部变量的生存期
void fun1()
{
int n = 10;
printf("n=%d , %p \n", n, &n);
printf("\n");//添加断点,监控内存
}
void main()
{
fun1();
printf("\n 执行1此完成");//添加断点,监控内存
fun1();
printf("\n 执行2此完成"); //添加断点,监控内存
system("pause");
}
函数执行到 函数体内的断点时
继续执行,直到 第一次执行结束,内存被回收。
接着执行第二次,内存重新分配
继续执行到第二次结束,内存再次被回收
这种内存分配方式叫自动分配内存,对于自动分配(栈分配),变量与其所在的代码块共存亡。
自动就是在栈上分配存储空间,系统会自动回收和清理。函数在调用是,从变量定义的地方开始创建,函数执行结束后,系统自动进行回收。
静态分配:
示例:全局变量的生存期
/*
全局变量,静态分配,其生命周期就是整个程序的执行周期,内存会一直存在,在 main 函数执行之前就创建
无论程序如何运行,如何被调用,内存空间一直都存在,直到整个程序结束才被系统回收。
*/
int ARR2[3] = { 4,5,6 };
void fun2()
{
int arr1[3] = { 1,2,3 };
printf("局部变量:%p , 全局变量:%p \n", arr1, ARR2);
printf("\n");
}
void main()
{
fun2();
printf("执行1完成\n");//添加断点,监控内存
fun2();
printf("\n 执行2完成\n"); //添加断点,监控内存
system("pause");
}
第一次执行结束,局部变量被回收,全局变量依然存在
第二次执行,局部表变量重新被分配内存,全局变量保持不变。
继续执行到第二次函数执行结束,局部变量再次被回收,全局变量不变。
静态分配,生命周期就是整个程序执行周期,内存会一直存在,main函数执行之前就创建,无论函数如何运行,如何调用,内存一直不会被回收,一直到程序结束才被系统回收。
动态分配内存:动态分配内存,动态回收内存,可以自己决定其生命周期
示例:动态分配和回收
void* createStorage()
{
//开辟一块存储空间,20个字节
void* p = malloc(sizeof(int) * 5);//在堆上分配存储空间,程序员自己分配和释放
printf("%p \n", p);
return p;
}
void main()
{
void* p = createStorage();
int* pInt = (int*)p;//空指针转换为 int 类型指针
for (size_t i = 0; i < 5; i++)
{
pInt[i] = i; //赋值
}
for (size_t i = 0; i < 5; i++)
{
printf("NO: %d = %d \n", i, pInt[i]);
}
free(pInt);//手动回收释放内存
system("pause");
}
程序执行 首先分配了未初始化的内存空间
程序接着执行,for 循环中对其进行了赋值
接着进行了输出打印
在进行 free 函数后 内存被回收释放
- 动态分配的生存期,从malloc开始,free结束
- 动态分配和静态分配的区别:静态分配一般在栈上,系统自己分配和回收内存。动态分配在堆上,由程序员自己决定分配和回收内存。
四 作用域与可见域:
在C程序代码中,变量的有效范围(源程序区域)称为作用域,能对变量,标识符进行合法有效的访问的范围(源程序区域)称为可见域;即:作用域是变量理论上的有效区域,而可见域是变量实际有效的区域,可见域是作用域的子集。
C语言中作用域的分类:
- 块作用域: 自动变量 (auto , register)和 内部静态变量 (static)具有块作用域,在一个块内声明的变量,其作用域从声明点开始,到该块结束为止。函数定义中声明的形参,其作用域限定在函数体内,与其他函数体中声明的同名变量不冲突,允许在不同的函数中使用相同的变量名,编译器将未这些变量分配不同的存储单元,不会混淆。
- 文件作用域: 外部静态变量(static)具有文件作用域,从声明点开始到文件末尾,(文件是指编译的基本单位 .c 文件)。
- 全局(程序)作用域: 全局变量 ( extern) 具有全局作用域,只要在使用前对其进行声明,便可以在程序(有若干文件组成)的任意位置使用全局变量。
示例 :
// fun1.c
#include<stdio.h>
#include<stdlib.h>
static int num = 100; //文件作用域声明,只能在本文件的可见域中使用
extern char name[10] = "holle word"; //全局作用域,可以在整个解决方案中使用
int arr[5] = { 1,2,3,4,5 }; // extern 关键字 加与不加 ,不影响全局作用域的范围
void run1()
{
for (size_t i = 0; i < 5; i++)
{
printf("NO: %d = %d \n", i, arr[i]);
}
}
void run2()
{
printf("num=%d \n", num);
printf("name=%s \n", name);
}
void main()
{
run2();
run1();
system("pause");
}
添加文件 fun2.c 引用 fun1.c 中的变量
// fun2.c
#include<stdlib.h>
#include<stdio.h>
#pragma region 声明外部变量引用
extern char name[10];
extern int arr[5];
extern int num;
#pragma endregion
void main()
{
printf("fun1的 name=%s \n", name);
printf("num=%d \n", num);
for (size_t i = 0; i < 5; i++)
{
printf("NO: %d = %d \n", i, arr[i]);
}
system("pause");
}
编译出错:
nun 变量 是 static 修饰的,限定在其声明的文件中使用,外部文件是无法访问的。注释掉 num 再次编译。
// fun2.c
#include<stdlib.h>
#include<stdio.h>
#pragma region 声明外部变量引用
extern char name[10];
extern int arr[5];
//extern int num;
#pragma endregion
void main()
{
printf("fun1的 name=%s \n", name);
//printf("num=%d \n", num);
for (size_t i = 0; i < 5; i++)
{
printf("NO: %d = %d \n", i, arr[i]);
}
system("pause");
}
正常输出。
总结:
语言内存分区四个分区:
-
void fun(register int n) // n 不在内存区域,在CPU内部寄存器
函数fun
:存储了代码区的地址,函数名存储了代码二进制段入口点的地址。 -
栈区:
void fun(register int n)
{
auto int a; //栈区分配存储空间
}
- 堆区:
void main()
{
int *p=(int *)malloc(sizeof(int) * 5); // 指针 p 在栈区,p 指向的内存地址在堆区,需要手动分配和释放
}
栈区,堆区都是动态存储区,栈区调用的时候分配,用完时回收再次分配。堆区,自己自由分配,自己决定何时分配和回收。代码区,静态区,与程序共存亡。
寄存器变量和自动变量必须放在函数内部,外部就会报错。全局变量的定义必须放在函数外部,全局变量的声明,拖长全局变量的可见域。
Static即可以放在函数内部,也可以放在函数外部,内部是静态局部变量,外部是静态全局变量。作为函数参数也可以。它限定作用域为当前C文件。而全局变量当前的工程任何一个C文件都可以调用。
作用域和可见域:
可见域就是实际上可以作用的区域,作用域理论上可以作用的区域,在某些场合,需要增加声明才可以访问。
函数的划分 :
函数也可划分为内部函数和外部函数,平时使用最多的是外部函数,如果不指明存储类别,编译器默认定义的函数为外部函数。外部函数的作用域是整个源程序的所有源文件,而内部函数只能在其所在的源文件中进行访问。
内部函数是加了Static,他的作用域只是当前C文件。外部函数的作用域是整个源程序的所有源文件
结构体的用法:
不同源文件中可以定义同名不同版本的结构体类型,在同一源文件中,同名结构体类型只能定义1次。否则,编译器会报错。
各种变量的作用域和存在性: