register:这个关键字请求编译器尽可能的将变量存在 CPU 内部寄存器中而不是通过内存寻址访问以提高效率。注意是尽可能,不是绝对,因为一个CPU的寄存器的数量是有限的,不可能把这些变量都放入寄存器中。
寄存器:数据从内存里拿出来先放到寄存器,然后 CPU 再从寄存器里读取数据来处理,处理完后同样把数据通过寄存器存放到内存里,CPU 不直接和内存打交道。
为啥要这么麻烦啊?因为速度。寄存器其实就是一块一块小的存储空间,只不过其存取速度要比内存快多。近水楼台先得月嘛,它离 CPU 很近,CPU 一伸手就拿到数据了,比在那么大的一块内存里去寻找某个地址上的数据快多了,同时寄存器的价格也比较高昂。
使用 register 修饰符的注意点
虽然寄存器的速度非常快,但是使用register
修饰符也有些限制的:register
变量必须是能被 CPU 寄存器所接受的类型。意味着register
变量必须是一个单个的值,并且其长度应小于或等于整型的长度。 而且 register
变量可能不存放在内存中,所以不能用取址运算符“&”来获取register
变量的地址。
2.1修饰变量
变量又分为局部和全局变量,但它们都存在内存的静态区。
extern
声明也没法使用他。准确地说作用域是从定义之处开始,到文件结尾处结束,在定义之处前面的那些代码行也不能使用它。想要使用就得在前面再加extern XXX
。static
修饰的变量总是存在内存的静态区,所以即使这个函数运行结束,这个静态变量的值还是不会被销毁,函数下次使用时仍然能用到这个值。static int j;
void fun1(void)
{
static int i = 0;
i ++;
}
void fun2(void)
{
j = 0;
j++;
}
int main()
{
for(k=0; k<10; k++)
{
fun1();
fun2();
}
return 0;
}
i 和 j 的值分别是什么,为什么?
2.2修饰函数
函数前加 static
使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。
在一段程序代码中,从内存高地址到内存低地址,依次分布着栈区、堆区、全局区(静态区)、常量区、代码区,其中全局区(静态区)中高地址分布着.bss段,低地址分布着.data段。总的分布如图所示。
全局区(静态区)
全局区有.bss段和.data段组成,可读可写。.bss段
- 未初始化的全局变量存放在.bss段。
- 初始化为0的全局变量和初始化为0的静态变量存放在.bss段。
- .bss段不占用可执行文件空间,其内容有操作系统初始化。
.data段
- 已经初始化的全局变量存放在.data段。
- 静态变量存放在.data段。
- .data段占用可执行文件空间,其内容有程序初始化。
- const定义的全局变量存放在.rodata段。
经常被误解为函数,sizeof
是关键字不是函数。
int i=0;
(A)sizeof(int);(B)sizeof(i); (C)sizeof int; (D)sizeof i;
思考一下:假设在 32 位系统下为什么A、B、D的结果为4,而C编译器提示出错呢.
一个 32 位的 signed int
类型整数其值表示法范围为:- 231~ 231 -1;8 位的char 类型数其值表示的范围为- 27 ~ 27 -1。一个 32 位的 unsigned int
类型整数其值表示法范围为:0~ 232 -1;8 位的 char
类型数其值表示的范围为 0~ 28 -1。
break
关键字很重要,表示终止本层循环。现在这个例子只有一层循环,当代码执行到
break
时,循环便终止。
如果把 break
换成 continue
会是什么样子呢?continue
表示终止**本次(本轮)**循环。当
代码执行到 continue
时,本轮循环终止,进入下一轮循环。
定义 const 只读变量,具有不可变性。
例如:
const int Max=100;
int Array[Max];
编译器通常不为普通 const 只读变量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的值,没有了存储与读内存的操作,使得它的效率也很高。
#define M 3 //宏常量
const int N=5; //此时并未将 N 放入内存中
int i=N; //此时为 N 分配内存,以后不再分配!
int I=M; //预编译期间进行宏替换,分配内存
int j=N; //没有内存分配
int J=M; //再进行宏替换,又一次分配内存!
const 定义的只读变量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的是立即数,所以,const 定义的只读变量在程序运行过程中只有一份拷贝(因为它是全局的只读变量,存放在静态区),而#define 定义的宏常量在内存中有若干个拷贝。#define 宏是在预编译阶段进行替换,而 const 修饰的只读变量是在编译的时候确定其值。#define 宏没有类型,而 const 修饰的只读变量具有特定的类型。
一般常量是指简单类型的只读变量。这种只读变量在定义时,修饰符 const 可以用在类型说明符前,也可以用在类型说明符后。例如:
int const i=6;
const int i=6;
定义或说明一个只读数组可采用如下格式:
int const a[3]={1, 2, 3};
const int a[3]={1, 2, 3};
const int *p; // p 可变,p 指向的对象不可变
int const *p; // p 可变,p 指向的对象不可变
int *const p; // p 不可变,p 指向的对象可变
const int *const p; //指针 p 和 p 指向的对象都不可变
这里给出一个记忆和理解的方法:先忽略类型名(编译器解析的时候也是忽略类型名),我们看 const 离哪个近。“近水楼台先得月”,离谁近就修饰谁。
const
修饰符也可以修饰函数的参数,当不希望这个参数值被函数体内意外改变时使用。例如:
void Fun(const int i);
告诉编译器 i 在函数体中的不能改变,从而防止了使用者的一些无意的或错误的修改。
const
修饰符也可以修饰函数的返回值,返回值不可被改变。例如:
const int Fun (void);
volatile 是易变的、不稳定的意思。很多人根本就没见过这个关键字,不知道它的存在。也有很多程序员知道它的存在,但从来没用过它。
volatile 关键字和 const 一样是一种类型修饰符,用它修饰的变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问
volatile int i=10;
int j = i;
int k = i;
volatile
关键字告诉编译器i
是随时可能发生变化的,每次使用它的时候必须从内存中取出 i
的值,因而编译器生成的汇编代码会重新从i
的地址处读取数据放在k
中。
相同点:二者都是常见的符合结构,都是由多个不同的数据类型成员组成;
不同点:联合体中所有的成员共用一块地址空间,即联合体只存放一个被选中的成员,内存空间是最长成员占用的空间,需要进行内存对齐。
结构体所有成员占用空间是累加的,其所有成员都存在,不同成员会存在不同的地址,内存空间等于所有成员 占用的空间之和,同样需要内存对齐。
#define
宏常量是在预编译阶段进行简单替换。枚举常量则是在编译的时候确定其值。在实际项目中,为了方便,可能很多数据类型(尤其是结构体之类的自定义数据类型),需要我们重新取一个适用实际情况的别名。这时候 typedef 就可以帮助我们。例如:
typedef struct student
{
//code
}Stu_st,*Stu_pst;
struct student stu1;和 Stu_st stu1;没有区别。
struct student *stu2;和 Stu_pst stu2;和 Stu_st *stu2;没有区别。
推荐阅读:
面试中常被问到的C语言基础知识值得收藏 (qq.com)
参考书籍:
[1]C primer
[2]C 专家编程
[3]C陷阱与缺陷
[4]C和指针
感谢阅读:)
inner peace
知行合一