Storage
存储类别
作用域
- 块作用域:其实就是花括号(在 C99 之前确实就是这样)
- for 循环,while 循环,do while 循环,if 语句所用值得代码,及时没有花括号,也算作用域的一部分
#include<stdio.h>
int main()
{
for(int i=0; i<1;i++)
printf("%d",i);
}
- 只有在变长数组中,形参名才有用:
void use_a_VLA(int n, int m, ar[n][m]); - 在 main 外边的变量,作用域是这个文件
链接
#include <stdio.h>
int giants = 5; // 文件作用域,外部链接
static int dodgers = 3; // 文件作用域,内部链接
int main()
{
return 0;
}
存储期
- 静态存储期:程序执行是一直存在——文件作用域
- 线程存储期:以__Thread_local声明一个独享,每个线程都获得改变量的私有备份
- 自动存储期:块作用域
- 动态分配存储期:malloc
| 存储类别 | 存储期 | 作用域 | 链接 | 声明方式 |
|---|---|---|---|---|
| 自动 | 自动 | 块 | 无 | 块内 |
| 寄存器 | 自动 | 块 | 无 | 块内, 使用关键字 register |
| 静态外部链接 | 静态 | 文件 | 外部 | 所有函数外 |
| 静态内部链接 | 静态 | 文件 | 内部 | 所有函数外, 使用关键字 static |
| 静态无链接 | 静态 | 块 | 无 | 块内, 使用关键字 static |
自动变量
可以显示的写:auto。(最好不要用 auto,因为和 C++不统一)。
我们可以实现 rust 中的“遮蔽”,C 里边叫“隐藏”
Demo
(base) kimshan@MacBook-Pro output % ./"hiding"
x in outer block: 30 at 0x7ff7bb130828
x in inner block: 77 at 0x7ff7bb130824
x in outer block: 30 at 0x7ff7bb130828
x in while loop: 101 at 0x7ff7bb130820
x in while loop: 101 at 0x7ff7bb130820
x in while loop: 101 at 0x7ff7bb130820
(这个最难理解,因为每次 while,里边的 x 都会重新声明,所以一直都是 101)
x in outer block: 34 at 0x7ff7bb130828
// hiding.c -- variables in blocks
#include <stdio.h>
int main()
{
int x = 30; // original x
printf("x in outer block: %d at %p\n", x, &x);
{
int x = 77; // new x, hides first x
printf("x in inner block: %d at %p\n", x, &x);
}
printf("x in outer block: %d at %p\n", x, &x);
while (x++ < 33) // original x
{
int x = 100; // new x, hides first x
x++;
printf("x in while loop: %d at %p\n", x, &x);
}
printf("x in outer block: %d at %p\n", x, &x);
return 0;
}
寄存器变量
可以显示的写:register。(程序会尝试把内容存在寄存器,但不保证一定能存到,可能会被降级成普通)
register int quick;
块作用域的静态变量
可以显示的写:static。(证明了本文件可访问的全局变量)而且只被初始化一次。
Demo
/* loc_stat.c -- using a local static variable */
#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++);
}
// (base) kimshan@MacBook-Pro output % ./"loc_stat"
// 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
外部链接的静态变量
使用外部的变量:extern。
- 如果用本文件内的全局变量,可以选择的写 extern
- 如果用别的文件的全局变量,必须要写 extern
- 不要用 extern 来创建新的变量,他是用来引用别的变量的
Demo
int Erupt; /* 外部定义的变量 */
double Up[100]; /* 外部定义的数组 */
extern char Coal; /* 如果Coal被定义在另一个文件,则必须这样声明*/
void next(void);
int main(void)
{
extern int Erupt; /* 可选的声明*/
extern double Up[];/* 可选的声明*/
...
}
void next(void)
{
...
}
注意,初始化不可以用变量
#include<stdio.h>
int x = 10; // 可以
int x2 = 2*x; // 不可以
int x3 = 2*10; // 可以
int main(){return 0;}
内部链接的静态变量
略
多文件
略
存储类别说明符
总结一下以上内容,一共六个关键字:
- auto
- register
- static
- extern
- _Thread_local
- typedef
只有 _Thread_local可以和 static 火化 extern 一起使用,其他的都只能单独使用。
存储类别和函数
函数也有存储类别
double gamma(double);
// 外部函数:本文件可调用,别的文件不能调用它
static double beta(int, int);
// 内部函数:本文件可以调用,别的文件不能调用它
extern double delta(double, int);
// 调用别的文件的函数,要这样先声明一下
存储类别的选择
外部存储类别方便但不安全,请不要什么都用外部存储类别。
随机数函数和静态变量
种子“next”是具有内部链接的静态变量,rand0 函数是具有外部链接的函数,可以被别的文件调用
/* rand0.c –– produces random numbers */
/* uses ANSI C portable algorithm */
static unsigned long int next = 1; /* the seed */
unsigned int rand0(void)
{
/* magic formula to generate pseudorandom number */
next = next * 1103515245 + 12345;
return (unsigned int) (next/65536) % 32768;
}
/* r_drive0.c -- test the rand0() function */
/* compile with rand0.c */
#include <stdio.h>
extern unsigned int rand0(void);
int main(void)
{
int count;
for (count = 0; count < 5; count++)
printf("%d\n", rand0());
return 0;
}
// (base) kimshan@MacBook-Pro Ch12 % gcc rand0.c r_drive0.c -o a
// (base) kimshan@MacBook-Pro Ch12 % ./a
// 16838
// 5758
// 10113
// 17515
// 31051
我们可以发现,种子是固定的,每次都是从16838这里开始,我们进一步写一个更新 seed 的函数
/* s_and_r.c -- file for rand1() and srand1() */
/* uses ANSI C portable algorithm */
static unsigned long int next = 1; /* the seed */
int rand1(void)
{
/* magic formula to generate pseudorandom number */
next = next * 1103515245 + 12345;
return (unsigned int) (next/65536) % 32768;
}
void srand1(unsigned int seed)
{
next = seed;
}
/* r_drive1.c -- test rand1() and srand1() */
/* compile with s_and_r.c */
#include <stdio.h>
#include <stdlib.h>
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); /* reset 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;
}
// (base) kimshan@MacBook-Pro Ch12 % gcc s_and_r.c r_drive1.c -o b
// (base) kimshan@MacBook-Pro Ch12 % ./b
// Please enter your choice for seed.
// 100
// 12662
// 23392
// 22561
// 20718
// 6314
// Please enter next seed (q to quit):
// 200
// 25325
// 25316
// 2367
// 19320
// 9131
// Please enter next seed (q to quit):
// q
// Done
```
malloc 和 free
double *p = (double *)malloc(30*sizeof(double));
注意是 double *,它要指向 double,而不是指向 30 个大小的 double!
if(p==NULL)
{
puts("Memory out!");
exit(EXIT_FAILURE);
}
记得要 free
free(p);
也可以用 calloc
double *q = (double *)malloc(30, sizeof(double)); // 就一点区别
ANSI C 类型限定符
const
- 幂等性
const const const int n=6;
等同于
const int n=6;
所以我们可以这么写
typedef const int zip;
const zip q=8;
- 指针与 const
#include <stdio.h>
int main()
{
{ // const 在 * 左边
double d1 = 100.0;
double d2 = 100.0;
const double *p1 = &d1; // 限定的是double 是 const
// double const *p1 = &d1; // 一样
p1 = &d2; // 可以,指针可变
// *p1 = d2; // 不行, 值不能变
}
{ // const 在 * 右边
double d1 = 100.0;
double d2 = 100.0;
double *const p1 = &d1; // 限定的是double 是 const
// p1 = &d2; // 不行,指针不能变
*p1 = d2; // 可以, 值可以变
}
return 0;
}
全局变量很危险,所以使用 const 限制它会很好!
volatile
volatile 和 const 语法一样,volatile 用于给编译器优化用。比如一个地址存的是当前的时钟时间,需要不停的改变。
GPT
volatile 关键字在以下几种情况下是必须的:
-
硬件寄存器映射:当变量被映射到硬件寄存器时,这些寄存器的值可能会在没有显式软件操作的情况下改变(例如,由硬件事件触发)。在这种情况下,声明为
volatile可以防止编译器优化掉对这些变量的读取和写入操作。volatile uint32_t * hardwareRegister = (uint32_t *)0x12345678; // 假设地址是硬件寄存器的地址 -
多线程共享变量:在多线程程序中,如果多个线程共享一个变量,并且至少有一个线程可能会在任何时候修改它,那么这个变量应该被声明为
volatile,以防止编译器假设它在没有显式同步的情况下保持不变。volatile int sharedFlag = 0; // 在多个线程中共享的标志 -
中断服务例程(ISR):在中断服务例程中使用的变量,或者在主程序中被中断服务例程修改的变量,应该声明为
volatile,因为中断可以在程序的任何地方发生,从而改变这些变量的值。volatile int interruptCounter = 0; // 在中断服务例程中递增的计数器 -
非原子操作:在某些情况下,对变量的读写操作不是原子的,特别是在32位或更大数据类型的变量在32位处理器上。如果变量可能被不同的线程或中断以非原子方式修改,那么它应该被声明为
volatile。volatile long nonAtomicValue; // 在多线程环境中可能被非原子方式修改的值 -
操作系统特定的同步机制:在某些操作系统中,特定的同步机制(如信号量、事件等)可能要求相关的变量声明为
volatile,以确保编译器不会对它们的访问进行优化。 在所有这些情况下,使用volatile关键字可以确保编译器生成的代码能够正确地反映变量可能的变化,从而避免由于编译器优化而导致的潜在错误。然而,需要注意的是,volatile并不提供任何线程同步机制,它只是告诉编译器不要对变量进行不必要的优化。对于多线程同步,仍然需要使用互斥锁(mutexes)、信号量(semaphores)或其他同步机制。
restrict (C99)
GPT
restrict 是 C99 标准引入的一个关键字,用于限定指针的属性。当一个指针被指定为 restrict 时,它表示该指针指向的对象在当前作用域内是唯一的,即不会有其他指针同时指向该对象。这样可以允许编译器进行更多的优化,尤其是在涉及到内存操作的场合。
具体来说,当你使用 restrict 关键字来修饰一个指针参数时,你是在告诉编译器,除了通过这个指针以外,没有其他途径可以访问到这个对象。这意味着编译器可以安全地进行一些假设,例如它可以放心地将数据缓存到寄存器中,而不必担心会有其他的指针导致缓存失效。
举个例子,考虑下面的函数原型:
c复制
void my_function(restrict int *ptr);
在这个例子中,my_function 函数接受一个类型为 int 的指针作为参数,并且这个指针被标记为 restrict。这就意味着在 my_function 内部,只有通过 ptr 指针才能访问到它所指向的数据,没有任何其他指针可以同时指向这块内存区域。
请注意,restrict 关键字的用法有一定的限制。首先,它只能用于限定指向对象的指针,而不能用于标量类型。其次,restrict 只能用来修饰指针本身,不能直接作用于指针所指的对象的类型上。最后,restrict 关键字的使用还需要依赖于具体的编译器和平台支持。
总结一下,restrict 关键字的主要目的是为了帮助编译器进行更好的优化,尤其是对于那些涉及大量内存操作的代码。通过明确指出某块内存的唯一访问路径,开发者可以帮助编译器减少不必要的内存屏障和缓存刷新,从而提高程序的执行效率。
Demo
#include <stdio.h>
void copy_memory(const int *restrict src, int *restrict dst, size_t n)
{
for (size_t i = 0; i < n; ++i)
{
dst[i] = src[i]; // 编译器可以假设src和dst不会重叠
}
}
int main()
{
size_t n = 10;
int src_array[n];
int dst_array[n];
// 初始化源数组
for (size_t i = 0; i < n; ++i)
{
src_array[i] = i;
}
// 调用函数,使用restrict关键字
copy_memory(src_array, dst_array, n);
// 打印目标数组来验证拷贝是否成功
printf("Copied array:\n");
for (size_t i = 0; i < n; ++i)
{
printf("%d ", dst_array[i]);
}
printf("\n");
return 0;
}
_Atomic (C11)
并发中的原子性。一个进程访问他时,别的进程不能访问。