全局变量
1、全局变量的定义与注意点
1、定义在函数外面的变量是全局变量
2、全局变量具有全局的生存期和作用域
3、它们与任何函数都无关
4、在任何函数内部都可以使用它们
上代码
#include<stdio.h>
int f(void);
int gAll = 12;
int main()
{
printf("in main gAll=%d\n",gAll);
f();
printf("now in main gAll=%d\n",gAll);
return 0;
}
int f(void)
{
printf("in f gAll=%d\n",gAll);
gAll+=2;
printf("now in f gAll=%d\n",gAll);
return gAll;
}
可以看到,全局变量的改变与任何一个函数都有关系
该代码的打印结果为
12
12
14
14
变量gAll先在main函数中打印在函数外初始化的值12,之后在f函数中先打印12,在加2之后,打印14,返回main函数的值也是14,这说明了:
全局变量如其名,具有全局的生存期和作用域,函数可以对它进行操作。确实很方便。
但要注意:
全局变量的初始化
1、没有做初始化的全局变量会得到0值
2、全局变量指针会得到NULL值
3、只能用编译时已知的值来初始化全局变量–>编译之前就已确定的值 ,常量等
4、它们的初始化发生在main函数之前 -->也就是说它们的初始化值不能用函数中的值
#include<stdio.h>
int f(void);
int a;
int gAll = 12;
int *p;
int main()
{ printf("%d %p",a,p);
printf("in main gAll=%d\n",gAll);
f();
printf("now in main gAll=%d\n",gAll);
return 0;
}
int f(void)
{
int gAll = 2;
printf("in f gAll=%d\n",gAll);
gAll+=2;
printf("now in f gAll=%d\n",gAll);
return gAll;
}
打印全局变量a和p的值,会得到0,这说明了全局变量,在没有初始化之前,会被初始化为0。
tips:在函数外的数组也会被初始化为0。
#include<stdio.h>
int i = 3
int a = i
int main()
{
return 0;
}
这样的代码是错误的,i虽然是一个全局变量,即使它被赋值了,它不能给全局变量a赋值。因为在编译时,编译器并不知道i的值。它被视作一个变量。
但如果:
#include<stdio.h>
const int i = 3
int a = i
int main()
{
return 0;
}
在i前加上const,此时它被视作一个确定的值(视作常量),因此a =3,编译能够通过。
静态本地变量
1、在本地变量定义时加上static修饰符就成为静态本地变量
2、当函数离开时,静态本地变量会继续存在并保持其值
3、静态本地变量的初始化只会在第一次进入这个函数时做,以后进入函数时会保持上次离开的值
例如:
#include<stdio.h>
int f()
{
static int a=2;
printf("a=%d\n",a);
a+=2;
printf("a=%d\n",a);
}
int main()
{
f();
f();
f();
return 0;
}
输出结果为:
2
4
4
6
6
8
在main函数中调用了3次main函数,但a的值并没有在每一次调用时发生变化,这就说明静态本地变量的初始化只有在第一次进入这个函数时执行了,并且每次被调用时会保持上次的值。例如,在本例中,a在被调用时的值分别为2、4、6。
静态本地变量的地址
本地变量的地址与全局变量不在一起存放。
1、静态本地变量实际上是特殊的全局变量
2.全局变量和本地变量位于相同的存储区域
3、静态变量具有全局的生存期,但作用域为一个函数
4、static在这里的意思是局部作用域(本地可访问)
例如:
#include<stdio.h>
int a = 3;
int main()
{
static int b =5;
int c=0;
printf("%p\n",&a);
printf("%p\n",&b);
printf("%p\n",&c);
return 0;
}
输出a、b、c的地址,会发现全局变量a和静态本地变量b地址相邻,而本地变量c则在另一块存储单元上,这说明全局变量和静态本地变量类型相似,实际上,静态本地变量是特殊的本地变量,只是作用域比本地变量小,只是它所在的那个函数。
返回指针的函数
1、返回本地变量的地址是不安全的–>本地变量的生存期只在函数内,存放本地变量的地址在使用后会被收回去,以供别的变量存储值
2.返回全局变量或本地变量的地址是安全的
3、返回在函数内malloc的内存是安全的,但是容易造成问题
4、最好的做法是返回传入的指针(只改变传入函数的那个值)
例如:
#include<stdio.h>
int* f(void);
void g(void);
int main()
{
int *p=f();
printf("*p=%d\n",*p);
g();
printf("*p=%d\n",*p);//*p所指的地址并没有变化,改变的是地址所指的值,f()函数结束后,i变量的存储单元变得自由,存储了g()中的变量值
return 0;
}
int* f(void)
{
int i = 12;
return &i;
}
void g(void)
{
int k = 24;
printf("k=%d\n",k);
}
在上述例子中,f()中的i变量地址所指的空间在函数调用结束后变得自由,以存储下一个变量,所以g()函数中的k占据了这块空间。用来存储其值。所以,函数返回本地变量的地址是不安全的,因为你并不知道它是否还是你想要的那个值。
结构体
1、结构体是什么?
结构体是由一批数据组合而成的一种新的自定义类型。
2、为什么需要有结构体的类型?
内置类型不能表示所有的场景,比如:学生群体;描述学生:name、age、gender、height。
struct Student{
char name[20];
int age;
char gender[3];
double height;
};
3、结构体内存对齐
(1)为什么需要内存对齐?
1、平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2、性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐;
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问仅需要一次。
(2)如何进行内存对齐?
这里分为两大步骤:
1、使结构体中每个成员变量都处在对齐的地址上:
a、结构体中的第一个成员变量在偏移量为0的地址上;
b、剩余的变量,对齐规则:min(该变量类型,默认对齐数),然后检测该变量相对于结构体起始位置的偏移量是不是该min的整数倍。不是的话,需要在该变量之前补齐字节数。
2、让结构体整体对齐:
使得结构体大小为 min(结构体成员变量类型最大者,默认对齐数)的最小整数倍。
(3)求结构体中某个成员变量相对于结构体起始位置的偏移量?
offsetof(结构体类型, 成员名字)
模拟实现该宏:
#define offsetof(s, m) (size_t)&(((s*)0)->m)
//让编译器将0号地址单元开始的一块内存作为S的结构体进行解析。
4、位段
位段为一种数据结构,可以把数据以位的形式紧凑的存储,并允许程序员对此结构的位进行操作。
位段的好处:
跟结构体相比,位段可以达到同样的效果,但是可以很好的节省空间。
位段的缺点:
位段的内存分配与对齐的实现方式依赖于具体的机器和系统,在不同的平台可能有不同的结果,这导致了位段在本质上是不可移植的。
位段内存对齐:
a、前后类型相同,比特位能共用则共用,否则重新开辟对应类型的空间;
b、前后类型不一致,重新开辟空间。
5、联合体
联合体也是一种特殊的自定义类型,这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块内存空间。
联合的特点:
联合的成员共用同一块空间,这样一个联合变量的大小,至少是最大成员的大小。
联合大小的计算:
联合的大小至少是最大成员的大小;
当最大成员大小不是最大对齐数的整数倍的时候,就到对齐到最大对齐数的整数倍。
6、什么是大小端?
大端模式:指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中。
小端模式:指数据的低位保存在内存的低地址中,而数据的低位,保存在内存的高地址中。
为什么有大小端?
因为在计算机系统中,我们是以字节为单位的,每个地质单元都对应着一个字节,一个字节位8bit,但是在C语言中除了8bit的char之外,还有16bit的short、32bit的long(具体看编译器),对于位数大于8位的处理器,由于寄存器大于一个字节,那么必然存在一个如何将多个字节安排的问题,因此就导致了大端存储模式和小端存储模式。
大小端跟操作系统是否有关系?
跟操作系统没有关系,CPU是大小端存储的决定因素。
如何测试一个机器为大端还是小端?
//利用联合体判断
int check_sys()
{
union
{
int i;
char c;
}un;
un.i = 1;
return un.c;
}
int main()
{
int ret = check_sys();
if (ret == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
system("pause\n");
return 0;
}