c笔记 存储类别, 链接与内存管理 20210306
存储类别
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)作为参数,该情况下仅能通过返回值获得值。