欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

c笔记 存储类别, 链接与内存管理 20210306

程序员文章站 2024-02-02 15:20:58
...

存储类别

c语言储存的每个值都占用一定内存(即对象(object)), 对象可储存复数个值, 储存适当值时一定具有相应的大小。
访问对象通过声明变量实现。

int entity  = 3;
int *pt = &entity;
int ranks[10];

创建标识符(identifier)用来指定(designate)特定对象的内容。
pt是标识符,其指定了一个储存地址的对象;pt并非标识符,然而确实指定了一个对象, 与entity指定的对象相同。指定对象的表达式称为左值,entity与pt都是左值, * (rank + 2 *entity)也是左值。ranks的声明创建了可容纳10个int型元素的对象, 该数组每个元素也是一个对象。

const char *pc = "Behold a string literal!";

pc可重新指向其他字符串, 是可修改的左值(modifiable lvalue)。 *pc不可修改, 不是可修改的左值。

存储期(storage duration)指对象在内存中的保留时间
作用域(scope)和链接(linkage)表明程序哪些部分可以使用该标识符。
不同存储类别具有不同的存储期, 作用域和链接。
对象可存在于程序执行期, 也可仅存在于函数的执行期。 对于并发编程, 对象可在特定线程中存在。

作用域

一个c变量作用域可是块作用域, 函数作用域, 函数原型作用域或文件作用域。
函数作用域仅用于goto语句的标签。
变量定义在函数之外具有文件作用域。文件作用域变量也称之为全局变量(global variable)。实际可见范围是整个翻译单元(translation unit)
如果程序由多个源文件组成, 那么该程序也将由多个翻译单元组成, 每个翻译单元对应一个源代码文件和它所包含的所有文件。

链接

c变量具有3种链接属性:外部链接, 内部链接, 或无链接。
具有块作用域, 函数作用域或函数原型作用域的变量都是无链接变量, 即这些变量属于定义其的块, 函数或原型私有。
具有文件作用域的变量是为外部链接或内部链接。

int giants = 5; 		//文件作用域外部链接
static int dodgers = 3;    //文件作用域内部链接
int main()
{
}

存储期

c对象有四种存储期:静态存储期, 线程存储期, 自动存储期, 动态分配存储期。
文件作用域变量具有静态存储期(程序执行期间一直存在)
线程存储期用于并发程序设计, 具有线程存储期的对象从被声明开始到线程结束一直存在。以关键字 _Thread_local声明一个对象时, 每个线程获得其私有备份。
块作用域变量通常具有自动存储期,从进入块存在到退出块 其变量占用的内存可用于储存下一个被调用函数的变量。(变长数组(VLA)存储期为从声明至退出块)
块作用域也可具有静态存储期, 加上关键字static:

void more (int number)
{
	int index;
	static int ct = 0;
	return 0;
}

变量ct储存在静态内存中,可以给其他函数提供该存储区的地址使之间接被访问(指针形参或返回值)

自动变量

自动存储类别变量具有自动存储期, 块作用域, 无链接
强调不要把该变量改为其他存储类型, 可使用关键字auto

int main(void)
{
	auto int plox;
}

关键字auto是存储类别说明符(storage-class apecifier), 在c++与c中的用法完全不同。
如果内层块中声明的变量与内层块中的同名, 内层块会隐藏外层块的定义。

1.没有花括号的块

作为循环或if语句的一部分, 即使不用花括号也是一个块。整个循环是它所在块的子块(sub-block), 循环体是整个循环块的子块。同理if语句也是一个块, 其子语句是if语句的子块。

2.自动变量的初始化

自动变量不会初始化, 除非显式初始化它

int main(void)
{
	int repid;
	int tents = 5;

tents初始化为5, repid变量的值是之前分配给repid的所在空间的任意值。
可以用非常量表达式(non-constant expression)初始化自动变量。

int main(void)
{
	int rush = 1;
	int rance = 6 * rush;
}

寄存器变量

寄存器变量储存在cpu的寄存器中, 访问与处理速度更快, 无法获取寄存器变量的地址。
寄存器变量与自动变量大致相同, 块作用域, 无链接, 自动存储期。使用存储类别说明符register可声明。

int main(void)
{
	register int quick;
}

register类别是否声明成功取决于寄存器或最快可用内存的数量。

块作用域的静态变量

静态变量(static variable), 具有文件作用域的变量自动具有静态存储期。也可自行创建具有静态存储期, 块作用域的局部变量, 这种变量具有块作用域, 无链接, 但是具有静态存储期,以存储类别说明符static来声明。

#include <stdio.h>
void trystat(void);

int main(void)
{
	int count;
	for (count = 1; count <= 3; count++)
	{
		printf("Here comes iteration %d:\n", count);
		trystat();
	}
	return 0;
}
void trystat(void)
{
	int fade = 1;
	static int stay = 1;
	printf("fade = %d and stay = %d\n", fade++, stay++);
}
Here comes iteration 1:
fade = 1 and stay = 1
Here comes iteration 2:
fade = 1 and stay = 2
Here comes iteration 3:
fade = 1 and stay = 3

静态变量stay保存了被递增的值, fade每次都会被初始化, stay只在编译时被初始化一次, 若为显式初始化静态变量, 默认初始化为0。
原因:静态变量和外部变量在程序载入内存时已被执行完毕。
不可在函数形参中使用static:

int wontwork(static int flu); // 不允许

具有块作用域的静态变量称为称为局部静态变量, 一些老的文献也称之为内部静态存储类别(internal static storage class)

外部链接的静态变量

外部链接的静态变量具有文件作用域, 外部链接和静态存储期, 称为外部存储类别(external storage class),属于该类别的变量是外部变量(external variable)
通过把变量的**定义性说明(defining declaration)**放在函数外即可声明。 若一个源文件使用的外部变量定义在另一个源文件中, 必须用关键字extern在该文件中再次声明。

int Errupt; //外部定义的变量
double Up[100];	//同上
extern char Coal;	   //被定义在另一个文件中的静态变量
void next (void);
int main(void)
{
	extern int Errupt; // 可选声明
	extern double Up[];    //同上
...
}
void next(void)
{
...
}

声明Up数组时不用指明数组大小。
若块作用域中变量与文件作用域中的变量同名, 则文件作用域中的变量被隐藏。若要声明与外部变量同名的局部变量, 最好加上存储类别说明符auto。

1.初始化外部变量

外部变量若不显式初始化, 默认为0, 同时不可使用非常量表达式进行声明。

2.使用外部变量

外部变量对变量定义下的函数可见,可通过其标识符进行访问

3. 外部名称

c99, c11标准要求识别局部标识符的前63个字符和外部标识符的前31个字符。

4. 定义与声明

int tern = 1;
main()
{
	extern int tern;

第一次声明是定义式声明(defining declaration), 第二次声明是引用式声明(referencing declaration),关键字extern指示编译器去别处查询其定义。

内部链接的静态变量(static variable with internal linkage)

static int svil = 1; //静态变量, 内部链接
int main(void)
{

在函数内部可选择存储类别说明符extern进行引用式声明。

存储类别说明符

共6个:auto, register, static, extern,_thread_local, typedf。
不能使用多个存储类别说明符作为typedef的一部分,不过_Thread_local可以和static或extern一起用。
auto表明变量是自动存储期
register作用于块作用域的变量声明, 将其归为寄存器存储类别
static创建的对象具有静态存储期
extern声明的变量在块意外

函数存储类别

函数可以是 外部函数(默认)或静态函数, c99新增了内联函数。

double gamma(double); //外部函数
static double bete(int, int); //静态函数
extern double delta(double, int); // 外部函数

beta为静态函数。

存储类型选择

按需知道原则:尽量在函数内部解决该函数的任务, 只共享需要共享的变量。

随机数函数和静态变量

ANSI C 提供的rand函数(声明在stdlib.h中)可生成伪随机数

/* ANSI C 可移植算法*/
static unsigned long int next = 1;      //种子数字
unsigned int rand(void)
{
	next = next * 1103515245 + 12345;
	return (unsigned int)(next / 65536) % 32768;
}

该函数返回0~32767之间的值, 然而因其种子不变, 实际上是伪随机数。
可引入一个srand1()函数重置种子

/*s_and_r.c   -- 包含rand1()和srand()的文件*/
static unsigned long int next = 1;
int rand1(void)
{
	next = next * 1103515245 + 12345;
	return (unsigned int)(next / 65536) % 32768;
}
void srand1(unsigned int seed)
{
	next = seed;
}
/*测试rand1()和srand1() */
/* 与s_and_r.c一起编译 */
#include <stdio.h>
#include <stdlib.h>
extern char* s_gets(char* st, int n);
extern void srand1(unsigned int x);
extern int rand1(void);
int main(void)
{
	int count;
	unsigned seed;
	printf("Please enter your choice for seed.\n");
	while (scanf("%u", &seed) == 1)
	{
		srand1(seed);
		for (count = 0; count < 5; count++)
			printf("%d\n", rand1());
		printf("Please enter next seed (q to quit):\n");
	}
	printf("Done\n");
	return 0;
}

也可通过访问时钟系统初始化种子

#include <time.h>  // 提供time()函数原型
srand((unsigned int) time(0)); //初始化种子

time()接受time_t类型地址, 将时间值存入传入地址。 也可传入空指针(0)作为参数,该情况下仅能通过返回值获得值。