存储类别和内存管理
导读:关于存储类别的知识点
一个变量的存储类别由三个方面构成:1.作用域 2.链接 3.存储期,这三方面相互依存,不能分离。
1)作用域
作用域是程序访问变量的区域,一个变量的作用域是什么,程序就在什么区域去访问这个变量。
作用域分为四大类:
1.块作用域
块作用域指的是函数体块或者语句块,在这些块中声明的变量具有块作用域。
具有块作用域的变量,程序只能在相应的块中去访问,离开这个块,程序就不能去访问这个变量。
2.函数原型作用域
在函数原型中的形参变量具有函数原型作用域。(还应记住,函数原型中的形参所使用的变量名称可以和定义函数内容时所使用的变量名称不相同)
3.文件作用域
首先理解一下什么是翻译单元。
对于一个源代码文件,需要引用一个或者多个头文件,引用的头文件也是一个独立的文件,这些头文件中的内容在编译过后会覆盖#include这条指令,从而源代码和头文件中的内容就在一个文件中了,这个文件我们成为翻译单元。
作用域是这个翻译单元的变量具有文件作用域。
如果一个变量具有文件作用域,那么在这个翻译单元中的任何位置,都可以去访问这个变量。
4.全局作用域
在编写程序的时候,可能会有多个源代码文件同时进行编译,这时就会形成多个翻译单元,作用域为这些翻译单元的变量具有全局作用域。
如果一个变量具有全局作用域,那么在这些翻译单元中的任何位置(在整个程序中),都可以去访问这个变量。
2)链接
(可以把这个链接想象成平常见到的超链接,通过链接可以去访问变量)
1.外部链接
外部链接对应的是具有全局作用域的变量,通过这个外部链接,其他的翻译单元也能够去访问这个变量。
2.内部链接
内部链接对应的是具有文件作用域的变量,在一个翻译单元中,可以通过这个内部链接去访问这个变量。
内部链接的声明需要添加static关键字。
3.无链接
无链接对应的是具有块作用域的变量,在一个块内,无论变量是否使用static关键字进行声明,都是无链接的变量,因为程序进入这个块内才会访问这个变量,离开这个块就不会访问这个变量。
3)存储期
存储期指的就是一个变量的内存存在的时间。
1.自动存储期
自动存储期一般对应具有块作用域的变量,程序进入这些块内时,会为变量分配内存,离开这些块时,会将这些内存释放,如果使用static关键字声明的具有块作用域的变量,就不具有自动存储期,具有的是静态存储期。
2.静态存储期
静态存储期对应的是具有文件作用域的变量和具有全局作用域的变量,当程序编译完后,编译器就会为这些变量分配内存,当程序运行完成后,这些变量的内存才会释放掉(期间这些变量的内存一直存在)。
4)声明
声明包括两种类型:
1.定义式声明:对于变量来说,初次定义这个变量就是定义式声明。对于函数来讲,定义函数内容就是定义式声明。
2.引用式声明:对于变量来说,使用extern关键字在某处引用已经定义过的变量。对于函数来讲,声明函数模型就是引用式声明。
一、存储类别
1)自动存储(自动变量)
自动变量:具有块作用域、无链接、自动存储期的变量。当程序访问这些变量所在的块时,会为这些变量分配内存,并且访问他们,当程序离开相应的块时,会释放掉变量的内存,并且变量不可访问。
可以使用关键字auto来定义声明一个变量是自动变量,但是通常,我们默认不加存储类别关键字的变量就是自动变量。
2)寄存器存储(寄存器变量)
寄存器变量:在自动变量之前使用关键字:register。
double、long、longlong等占据内存较大的变量不能够定义声明为寄存器变量,因为寄存器可能没有空间去储存这些变量。
这个关键字并不是万能的,只有当寄存器中拥有能够储存这个变量的空间时,才会将这个自动变量储存在寄存器中(不储存在内存中)
由于不储存在内存中,因此无法查看这个变量的地址。如果使用register关键字定义声明失败,那么这个变量就是自动变量,但是即使定义声明失败,也不能查看这个变量的地址,因此这个关键字可以用来隐藏变量的地址。
寄存器变量具有块作用域、无链接、自动存储期,当程序访问块时,将变量储存在寄存器中,并且访问这个变量,当程序离开这个块时,变量从寄存器中释放,并且无法访问。
3)静态外部链接存储(外部变量)
外部变量:具有全局作用域、外部链接、静态储存期。
外部变量定义声明在所有函数的上面(一般来讲,我们把主函数放在其他函数的上面,因此外部变量声明在主函数的上面),不加任何存储类型关键字,这样的变量就是外部变量。虽然外部变量具有全局作用域,但是外部变量也是定义声明在某一个翻译单元中的,其他翻译单元要是想访问或使用这个变量,必须要用extern在所要引用的位置进行引用式声明。
外部变量可以不人为进行初始化,这样的话编译器会自动将其初始化为0,定义在外面的数组同样适用。
//外部变量的自动初始化
#include <stdio.h>
const int names;
int main(void)
{
printf("print the names in main() :%d\n",names);
return 0;
}
打印结果如下:
print the names in main() :0
4)静态内部链接存储(内部变量)
内部变量:具有文件作用域、内部链接、静态储存期。
内部变量同样定义声明在所有函数之上,只不过需要使用static关键字进行声明。在同一个翻译单元中,访问或使用内部变量不需要进行引用式声明,可以直接访问或者使用。(内部变量最好直接使用,不要用extern再次声明一遍,原因是内部变量如果使用const+static关键字,再使用extern关键字会出现关键字种类过多这种错误,程序不能够编译成功,因此内部变量最好不要进行引用式声明。)
5)补充
一般来讲,我们定义外部变量或者内部变量,都希望这个变量是个不可更改的变量,但是对于外部变量来讲,程序中的所有函数都能够更改他的值;对于内部变量来讲,同一个翻译单元中的所有函数都能够更改他的值,因此在定义声明一个外部变量或者内部变量的时候加上关键字const。
下面通过程序来了解extern关键字、内外部变量的定义声明和引用声明。
//文件1
//使用extern关键字来引用声明一个外部变量或者内部变量
#include <stdio.h>
#include "head_1.h"
const int names=5;//我们说过,定义声明外部变量或者内部变量的时候最好加上const关键字。
const double dates=2.6;
const static int times=30;//使用static关键字来声明这是一个内部变量。
int main(void)
{
printf("print the names in the main():%d\n",names);//虽然names是一个外部变量,但是定义声明在这个翻译单元中,因此使用的时候不需要引用声明。
extern const double dates;//这是对这个外部变量进行引用声明,前面说过,这个引用声明可以去掉。
printf("print the dates in the main() :%d\n",dates);
printf("print the times in the main() :%d\n",times);//内部变量最好不要在声明一遍
read_();//创建一个read_函数,这个函数定义声明在另一个源代码文件中。
return 0;
}
//文件2
//定义read_函数
#include <stdio.h>
void read_(void)
{
extern const int names;
extern const double dates;//如果外部变量没有定义在这个翻译单元中,若要访问这个外部变量,必须使用extern进行引用声明。
printf("print dates in names times\n");
for (int i=0;i<names;i++)
{
printf("Count %d %d\n",i+1,dates);
}
return;
}
//文件3
void read_(void);//最好是用一个头文件来储存函数原型的声明
6)静态无链接存储(静态无链接变量)
重点:如果想在被调函数中实现计数器,那么被调函数中的这个变量就要被定义声明为静态无链接变量。
静态无链接变量:具有块作用域、无链接、静态储存期。在自动变量之前加上static关键字即可。
对于静态无链接变量,程序在编译时就为这个变量分配了内存,当程序进入所在的块时,可以访问这个变量,当程序离开这个块时,不能够访问这个变量,但是内存只有在程序结束之后才会被释放掉。
通过一个程序来了解在被调函数中实现计数器:
//在被调函数中实现计数器设置
#include <stdio.h>
void loop_(void);
int main(void)
{
printf("Then we will creat a loop:\n");
int loop_times=5;
for (int i=0;i<loop_times;i++)
{
loop_();
}
return 0;
}
void loop_(void)
{
int i=1;
static int n=5;
printf("print the value of i :%d\n",i);
i++;
n++;
printf("print the value of n:%d\n",n);
putchar('\n');
return;
}
打印结果如下:
Then we will creat a loop:
print the value of i :1
print the value of n:6
print the value of i :1
print the value of n:7
print the value of i :1
print the value of n:8
print the value of i :1
print the value of n:9
print the value of i :1
print the value of n:10
我们结合打印结果分下一下:在被调函数中,变量i是自动变量,而变量n是静态无链接变量,因此编译器在编译的时候就对这个变量分配了内存,直到程序结束才会释放这个内存,所以当每次程序进入被调函数中,相当于忽略掉这条定义声明。
第一次进入被调函数,分配给变量i内存,并执行初始化,跳过static int n=5;这条语句,接着往下执行,i++,此时i=2,n++,此时n=6,离开被调函数,释放掉变量i的内存,因此i中的数据消失。
第二次进入被调函数,分配内存给变量i,并执行初始化,此时n=6,i=1,接着执行,i++,此时i=2,n++,此时n=7,离开被调函数,释放掉变量i的内存......
7)同名变量*
深入理解同名变量在不同的块中的运行规则。
如果在等级低的块中出现和等级高的块(或者是整个翻译单元又或者是多个翻译单元)中名字相同的变量,那么在低等级的块中,会自动将高等级中的那个同名变量隐藏。
//多层嵌套块中的同名变量
#include <stdio.h>
const int names=5;//定义一个外部变量
const static int dates=8;//定义一个内部变量
int main(void)
{
int x=5;//1
printf("print the names: %d\n",names);//2
printf("print the dates :%d\n",dates);//3
printf("print the x :%d\n",x);//4
{//块可以自己随意设置,不论在哪。
int x=8;//5
int names=10;//6
printf("print the names :%d\n",names);//7
printf("print the x :%d\n",x);//8
}
printf("print the names: %d\n",names);//9
printf("print the x :%d\n",x);//10
int i=0;//11
while (i<x)//12
{
int x=0;//13
x++;//14
i++;//15
printf("x=%d i=%d\n",x,i);//16
}
printf("print x and i :%d,%d\n",x,i);//`17
for (int i=1;i<x;i++)//18
{
int x=2;//19
printf("print the x :%d\n",x);//20
}
printf("print the x and i:%d\n",x,i);//21
do
{
int dates=3;//22
i++;//23
printf("print the dates :%d\n",dates);//24
} while (i<dates);//25
printf("print the dates :%d\n",dates);//26
printf("print the i :%d\n",i);//27
return 0;
}
打印结果如下:
print the names: 5
print the dates :8
print the x :5
print the names :10
print the x :8
print the names: 5
print the x :5
x=1 i=1
x=1 i=2
x=1 i=3
x=1 i=4
x=1 i=5
print x and i :5,5print the x :2
print the x :2
print the x :2
print the x :2
print the x and i:5 5
print the dates :3
print the dates :3
print the dates :3
print the dates :8
print the i :8
第2、3、4行在打印的时候就是正常的打印names和dates和x的值:5 8 5
第5、6行我定义声明了同名变量,由于重新定义变量的块等级低,因此会隐藏原来定义的变量,这两个变量相当于是重新定义的,地址和原来不同,此时在这个块中进行变量的打印,打印出的结果就是新定义的值。
第9、10行有从新打印names和x,由于程序离开了原来的块,所以在上一个块中定义的names和x内存被释放,并且解除隐藏。打印值是正常的。
第11行定义了一个自动变量i=0;
第12行是while循环,我们发现,这个循环循环了五次,因此可以判断出,while循环的循环头不属于while语句块中的部分,而是属于main()这个函数块中的部分。
第13、14、15、16行中,在while语句的块内(低级块)重新定义声明x,因此,在这个块中有关x的语句全部都使用这个新定义x(因为外层块中的x被隐藏)而i还是外层的i,由于每次进入这个循环,x都会从新定义声明,并进行一次++运算,所以循环五次,x都是1。
第17行,在main()中打印x和i,i经过循环后变为5,而离开循环块后,原来隐藏的x取消隐藏,回到5。
第18行,进入for循环,可以发现这个循环循环了4次,并且最后打印出来的x和i都是进如循环前的值,所以可以发现,for循环的循环头是比主函数低一级的块,而for循环的语句是比循环头更低一级的块,在内部定义的x=2,并不影响外部的x=5(每次检验循环头的时候x=5,进入循环的时候x=2)退出循环后打印出来的值又是进入循环之前的,并没受影响(如果不在循环内定义变量,那么会影响到main()块中的变量)。
第22行进入do循环,并且循环了三次,因此可以发现,do while循环的循环尾并不属于循环块中,他和main()块是一个等级,i从5到8正好循环三次(5-6,6-7,7-8),而dates在循环内部打印每次都是3。在外部的时候每次都是8。
结束!
二、函数的存储类别
函数同变量一样,也有存储类别。
1)外部函数
在定义函数的时候,什么关键字都不加,这个函数就默认是外部函数。
有了这个概念,我们在翻译单元较少的情况下可以不用将函数原型声明在头文件中,其他翻译单元使用的时候该函数的时候,只需要像引用式声明变量一样,使用extern进行引用即可。
2)内部函数
在定义函数的时候,加上static关键字,这个函数就是内部函数了,内部函数只能在本翻译单元中使用。
3)通过实例来理解一下内部函数和外部函数
//内部函数和外部函数
#include <stdio.h>
void read_(void);
static void listen_(void);//声明函数原型的时候要和定义一样,加上static关键字
//假设引用外部函数
//extern void write_(void);//这样就完成了引用式声明。
int main(void)
{
int x=4;
for (int i=0;i<x;i++)
{
read_();
listen_();
}
return 0;
}
void read_(void)//定义的时候,什么关键字都没有,因此这是一个外部函数,其他翻译单元使用需要进行引用式声明
{
int n=9;
n++;
printf("print the value of the n :%d\n",n);
return;
}
static void listen_(void)//我想把listen_()函数设置成内部函数,因此在定义最开始使用static关键字
{
char names='A';
names+=6;
putchar(names);
putchar('\n');
return;
}
打印结果如下:
print the value of the n :10
G
print the value of the n :10
G
print the value of the n :10
G
print the value of the n :10
G
三、函数形参中使用关键字
函数形参中可以使用的关键字:const、register、restrict、
不能够使用的关键字:static
如果要传递的实际参数(变量)在定义声明的时候使用了关键字,那么形参中也一定要使用关键字。
如果要传递的实际参数(变量)在定义声明的时候没有使用某些关键字,但是形参中有关键字,那么传递进来的这个参数将按照这个关键字进行执行。
这些关键字中:const可以用在变量也可用在指针身上,register只能用在变量身上,restrict只能用在指针身上。
四、time()函数、rand()函数、srand()函数
1)time()函数
time()函数原型声明在time.h这个头文件中。
在c语言中,time()函数的函数原型如下:
time_t time(time_t *second);
time_t是一种新的数据类型,这个数据类型表示的是秒数,转换说明是%ld(原因是秒数很大,是可以理解为long型)其实就是整数型的一种推广,这个函数接受一个time_t类型变量的地址,(储存秒的变量的地址),并且这个函数的返回值也是time_t类型的。
这个函数的作用是计算从1970年1月1日0时起,到运行程序截止,一共经过的秒数。
第一种用法,使用参数,不使用返回值。
//time()函数的使用1
#include <stdio.h>
#include <time.h>
int main(void)
{
time_t seconds;//设置一个time_t类型的变量。
time_t *ptime=&seconds;//由于time()函数接受的是time_t类型的指针,因此设置一个指针。
time(ptime);
printf("print the second from 1970-01-01-00:00 to now:%ld\n",seconds);
return 0;
}
第一次打印结果:
print the second from 1970-01-01-00:00 to now:1640056840
第二次打印结果:
print the second from 1970-01-01-00:00 to now:1640056864
第二种用法,不使用参数,使用返回值。
这种情况下,需要将time()的参数使用成空指针。
//time()函数的使用2
#include <stdio.h>
#include <time.h>
int main(void)
{
time_t seconds;
seconds=time(NULL);
printf("print the seconds from 1970-01-01-00:00 to now :%ld\n",seconds);
return 0;
}
打印结果如下:
print the seconds from 1970-01-01-00:00 to now :1640057094
2)rand()函数
伪随机数:伪随机数指的是所生成的数字并不是随机的。
我们可以想象一个很大的数组,里面的数字各不相等,rand()函数会按顺序取出这个数组中的数字,由于这个数组很大,因此rand()函数好似能够生成随机数,但是当我有两个这样的数组,并且生成次数能够到达第二个数组中时,就会生成和原来一模一样的数字。
rand()函数生成数字的范围是(想象出来的数组的范围):0~INT_MAX(int 所能表示的最大值)
我们先来分析一下rand的源代码:
//rand()函数的源代码
static unsigned long next=1;//我们把next叫做种子。
unsigned rand(void)
{
next=next*1103515245+12345;
return (unsigned)(next/65536)%32768;//强制转换成unsigned类型
}
rand()函数通过返回值来返回这个随机数,内部变量next=1,返回值是一个固定的值:16838,此时的next储存着另外的值,如果不结束程序,再次使用rand()函数,返回值是一个固定的:5758,此时next储存着的又是别的值......
当结束程序再次进去的时候,next=1,生成的数字还和原来一样,因为next的值是一样的。
通过一个程序验证:
//通过一个程序验证rand()函数的伪随机 文件一
#include <stdio.h>
extern unsigned rand(void);//引用声明其他翻译单元中的函数,这里有一点需要注意,为什么不引用声明next,原因是next声明定义在和
int main(void)//rand()一个翻译单元中,访问rand()的时候就会进入到那个翻译单元,因此很自然的就会在那个翻译单元中读取next,所以不用引用。
{
int times=3;
for (int i=0;i<times;i++)
{
peinrf("print the gauss number :%u\n",rand());//返回值是unsigned类型因此转换说明是%u
}
return 0;
}
//文件二
//rand()函数的源代码
static unsigned long next=1;
unsigned rand(void)
{
next=next*1103515245+12345;
return (unsigned)(next/65536)%32768;//强制转换成unsigned类型
}
第一次打印结果:
print the gauss number :16838
print the gauss number :5758
print the gauss number :10113
在前三次中,看似生成的是随机数,当我再次运行时:
print the gauss number :16838
print the gauss number :5758
print the gauss number :10113
生成的还是这些,顺序都不会发生改变,原因就是种子都是从1按照某个固定的顺序进行改变的。
3)srand()函数
由于rand()函数中的种子不是随机的,所以我如果把rand()中的种子设置成随机的,那么rand()生成的就是确确实实的随机数。
srand()函数的源代码:
//srand()函数的源代码
void srand(unsigned seed)
{
next=seed;
}
srand()函数的源代码非常简单,srand()函数接受一个seed,并且把seed的值赋值给next,所以如果seed是一个随机的数字,那么就能够使next随机(种子随机)。因此我们想到了time()函数。
由于rand()函数和srand()函数在stdlib.h头文件中,time()在time.h中,所以引用这两个头文件即可(这个seed我们用time()函数的返回值代替,值得注意的是,这个返回值我们需要强制转换成unsigned类型)
//使用rand()srand()time()生成随机数
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void)
{
srand((unsigned)time(NULL));//这一步我们叫做使用srand()函数设置随机种子 如果一次要生成多个随机数,srand()函数需要放置在循环外部。(意味着我在一次程序中我只需要设置一次随机种子)程序在读取time()函数的时候进行时间的生成,由于放在一个循环中,编译时间不足一秒,因此会是的time()函数的返回值一样,所以要一次性获得多个随机数的时候,将srand()函数放在循环之外。
int times;
puts("Enter the amount of the guess number you want:");
scanf("%d",×);
for (int i=0;i<times;i++)
{
printf("To create a guess number :%ld\n",rand());//这一步我们叫做使用rand()函数创建随机数
}
return 0;
}
第一次输入5的打印结果:
Enter the amount of the guess number you want:
5
To create a guess number :25580
To create a guess number :12645
To create a guess number :4220
To create a guess number :4705
To create a guess number :3618
第二次输入5的打印结果:
To create a guess number :25704
To create a guess number :27868
To create a guess number :27699
To create a guess number :1607
To create a guess number :2356
现在终于达到了我们所要的随机数。
4)根据随机数程序设计一个掷骰子程序
这个程序能够生成骰子的面数以及骰子的个数
//设置一个程序来掷骰子。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int input_sides(void);
int input_dice(void);
int main(void)
{
int dice_sides;
int number_dice;
int sum_dice_number;
int ren_val=1;
int loop_=0;
while (ren_val == 1)
{
puts("Enter the sides of the dice you want:");
dice_sides=input_sides();
puts("Enter the amounts of the dice you want to :");
number_dice=input_dice();
srand((unsigned)time(NULL));//设置随机种子 ,注意的点就是强制转换成unsigned类型 //让编译器读取srand()函数的时间岔开就好。
for (int i=0;i<number_dice;i++)
{
int agent=(rand()%dice_sides)+1;//编译器每次读到rand()函数都会生成一个新的随机数,并且这个agent是块作用域变量,每次进入都会重新赋值。
printf("The %d dice point is :%d\n",i+1,agent);
sum_dice_number+=agent;
}
printf("The sum of the points you roll is :%d that in %d dice(s) which has %d sides.\n"
,sum_dice_number,number_dice,dice_sides);
puts("Input q to quit the game and input a number to play again!");
ren_val=scanf("%d",&loop_);
getchar();//由于输入其他的,程序将停止,所以说只需要吃掉和换行符即可
}
return 0;
}
int input_sides(void)
{
int num;
int ren_val;
while ((ren_val=scanf("%d",&num)) != 1 || num<=1)
{
if (ren_val != 1)
{
puts("Please enter a number like 2!");
while (getchar() != '\n')
{
continue;
}
}
else
{
puts("Please enter a number that bigger than 1!");
getchar();//吃掉换行符
}
}
return num;
}
int input_dice(void)
{
int num;
int ren_val;
while ((ren_val=scanf("%d",&num)) != 1 || num<=0)
{
if (ren_val != 1)
{
puts("You need to enter a number like 2!");
while (getchar() != '\n')
{
continue;
}
}
else
{
puts("Must have 1 dice at least!");
getchar();
}
}
return num;
}
第一次输入4,4:
Enter the sides of the dice you want:
4
Enter the amounts of the dice you want to :
4
The 1 time point is :1
The 2 time point is :1
The 3 time point is :3
The 4 time point is :1
The sum of the points you roll is :6 that in 4 dice(s) which has 4 sides.
Input q to quit the game and input a number to play again!
q
第二次输入4,4:
Enter the sides of the dice you want:
4
Enter the amounts of the dice you want to :
4
The 1 time point is :3
The 2 time point is :3
The 3 time point is :1
The 4 time point is :1
The sum of the points you roll is :8 that in 4 dice(s) which has 4 sides.
Input q to quit the game and input a number to play again!
q
可以发现,程序是随机的。
五、内存分配函数
1)malloc()函数、free()函数&&动态数组
1.malloc()函数可以人为的在内存中创建一块牛内存,叫做内存块,在这个内存块中,还能够设置一个个的小单元,内存块的大小以及小单元的大小都是人为设置的。
2.malloc()函数的返回值返回的就是这个内存块首字节的地址(返回一个指针)。(我们之前所说的数组的首元素的地址,其实就是他相应内存块的首字节的地址)
3.使用malloc()函数相当于创建了一块动态内存,这块动态内存只有在程序结束的时候才会被释放,我们一般的做法是使用free()函数,在使用完成malloc()函数分配的内存后就将这块内存释放掉。
4.free()函数的参数是使用malloc()函数分配的内存的首地址。
5.动态数组,用malloc创建的内存块能够以小单元的形式存在,类似于数组,在设置内存块大小的时候可以使用变量,因此叫做动态数组。由于必须要有一个指针去指向这个动态数组(没有的话极易使数组流失在内存中,也就是这个数组存在,但是我找不到他,叫做内存泄漏),因此这个指针就相当于我们所说的数组名。
6.注意,这两个函数都是stdlib.h头文件中的函数。
malloc()函数的使用方法:
//malloc()函数的使用
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *ptint;
ptint=(int *)malloc(30*sizeof(int));//1
printf("print the adress of the ptint :%p\n",ptint);//2
printf("print the adress od the ptint+1 :%p\n",ptint+1);//3
free(ptint);//4
return 0;
}
打印结果如下:
print the adress of the ptint :0000000000C91420
print the adress od the ptint+1 :0000000000C91424
第一行中:malloc()的参数写法很规范,指的是30个int内存,也就是这个内存块中能够储存30个int类型的值,返回一个指向首字节的指针,默认状态下,malloc()创建的内存块,一个小单元是一个char内存,通过下面程序来理解:
//深入剖析malloc()函数
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
void *point;
point=malloc(30*sizeof(int));
printf("print the adress of point :%p\n",point);
printf("print the adress of point+1 :%p\n",point+1);
return 0;
}
打印结果:
print the adress of point :00000000009E1420
print the adress of point+1 :00000000009E1421
在这里,设置一个通用指针,void *point; 这个指针将自动匹配数据类型。
可以发现,对指针+1表示的是动态数组中下一个小单元的首字节地址(下一个小单元地止),地址由20变成21,也就是加了一个字节,因此可以发现,一个小单元就是一个char的内存。
接着分析,
在第一行中,malloc前面使用了(int* ),这相当于将这个将返回的地址强制转换成int类型(将返回的指针强制转换成int类型)
第二行中:打印首单元地址是:20
第三行中:打印第二个小单元的地址是:24,相差4字节,正好是int内存,这也证实了强制转换成int类型指针的作用。
第四行:养成良好习惯,使用完动态内存及时释放。
2)restrict关键字
restrict关键字只能使用在指针身上,并且当这个指针是某一块内存唯一的访问方式的时候,restrict才对其生效(才能够进行优化)否则是无效的。
注意:restrict关键字夹在*和指针名称之间,如果指向动态数组的指针,一般我们习惯加restrict关键字,养成好习惯。
在下面的程序中,对于我们的动态数组,int * restrict ptint_2=(int* )malloc(30*sizeof(int));,这里面的ptint_2就是malloc()函数分配的内存块的唯一的访问方式,对ptint_2加restrict关键字进行声明,会对接下来ptint_2访问这块内存中的数据或者修改内存中的数据做出优化。
而names这个数组这块内存,除了names能够访问,ptint_1这个指针也能够访问,因此这块内存存在着两个或者两个以上的访问方式,所以能够访问这块内存的指针都不能使用restrict关键字进行优化,尽管声明的过程中使用了restrict关键字,但是对其没有作用。
//restrict()关键字
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int names[10]={1,2,3,4,5,6,7,8,9,0};
int * restrict ptint_1=names;
for (int i=0;i<30;i++)
{
*names+=3;
*names+=6;
}
printf("print the value of the changed first number of the array:%d\n",*names);
int * restrict ptint_2=(int* )malloc(30*sizeof(int));
*ptint_2=1;
for (int i=0;i<30;i++)
{
*ptint_2+=3;
*ptint_2+=6;
}
printf("print the value of ptint :%d\n",*ptint_2);
return 0;
}
打印结果:
print the value of the changed first number of the array:271
print the value of ptint :271
分析一下如何进行优化的,编译器看到ptint_2有关键字restrict进行声明,因此会对ptint_2的运算进行优化,把第二个循环中,ptint_2+=3;ptint_2+=6;优化成ptint_2+=9;而第一个循环中则不会。

浙公网安备 33010602011771号