C语言笔记之关键字(一)
2016-03-10 16:01
232 查看
register:
请求编译器尽可能的将变量存储在CPU内部寄存器,提高访问效率。补充:寄存器是一块一块小的存储空间,其存取速度比内存(大地址)要快得多,离CPU很近。register变量可能不存放在内存中,所以不能用取地址运算符
static:
修饰变量,但又分为局部变量和全局变量,但都存在内存的静态区,不过作用域不同。前者在函数体里面定义的,之鞥在这个函数里调用了,即使该函数的运行结束,其值仍不会被销毁,在下次使用时值也一样。而后者,作用域从定义之处开始,到文件结尾处结束,在定义之处的前面要想使用,也得加extern在其他文件里即使用extern也不能使用。
该代码的运行结果为:i从1加到10.而j保持1不变。
修饰函数。值对函数的作用域仅局限于本文件。C++对static赋予新的含义,下次专门开专题来写啊啦啦啦。
sizeof:
首先需要注意的是sizeof是关键字而不是函数。
此处编译会报错。因为int 之前加关键字,系统会以为是类型扩展,而sizeof i是可以编译通过的。所以说,sizeof在计算变量所占空间大小时,括号可以省略,而计算类型大小时括号不能省略。
signed,unsigned:
答案是255.让我们来一起分析分析,for循环内,当i的值为0时,a[0]的值为-1,-1在内存里面用补码存储,为0xff,-2的补码是0xfe…当i的值为127时,a[127]的值为-128,而-128是char类型数据能表示的最小的负数。当i继续增加时,a[128]的值发生溢出,丢弃最高位,0x7f,当i继续增加到255的时候,-256的补码的低八位为0。然后当i增加到256时,-257的补码的低八位全为1即OXff.经分析,a[0]到a[254]里面的值都不为0,而a[255]为0。strlen函数是计算字符串长度的,并不包含字符串最后的’\0’.
float变量:
不能直接与0值进行比较
case:
void:
字面意思是空类型,void*则为空类型指针,可以只想任何类型的数据。void多用于对函数参数的限定和对函数返回的限定。
不能对 void*进行算法操作。如
标准认为,进行算法操作符的指针必须知道其指向数据类型大小的,即必须知道内存目的地址的确切值。void不能代表一个真实的变量,它的出现是一种抽象的需要。
return:
用来终止一个函数,并返回后表表达式的值。但return语句不可返回只想“栈内存”的“指针”(如局部变量),因为该内存在函数体结束时被自动销毁。
const:
在vc里分别创建.c和.cpp文件,发现在.c文件中编译器会提示出错,而在.cpp文件中则顺利运行。证明在c语言中,const修饰的是只读属性的变量,而在C++里,扩展了属性。case语句后面不可以是const修饰的只读变量。编译器通常不为普通const只读变量分配存储空间,而是将他们保存在符号表红,使得它成为一个在编译期间的值,没有了存储与读内存的操作,使得效率高。const的定义从汇编的角度来看,只是给出了对应的 内存地址,而不是#define一样给出的是立即数。所以,const定义的只读变量在程序运行时只有一份拷贝(因为他是全局的额只读变量,存放在静态区),而#define定义的变量在内存中有若干份拷贝。#define宏是在预编译阶段进行替换,而const修饰的只读变量是在编译的时候确定其值,宏没有类型,而const修饰的只读变量有特定的类型。
volatile:
1.编译器对访问该变量的的代码不再进行优化,从而可以提供对特殊地址的稳定访问。
比如要往某一地址送两条指令:
以上程序compiler可能做优化而成:
volatile int *ip = …;
*ip = 1;
*ip = 2;
此时,即使你要compiler做优化,它也不会把两次付值语句间化为一。它只能做其它的优化。这对device driver程序员很有用。
2.表示用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能把他放在cache或寄存器中重复使用。volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改。
用volatile关键字声明的变量i每一次被访问时,执行部件都会从i相应的内存单元中取出i的值。没有用volatile关键字声明的变量i在被访问的时候可能直接从cpu的寄存器中取值(因为之前i被访问过,也就是说之前就从内存中取出i 的值保存到某个寄存器中),之所以直接从寄存器中取值,而不去内存中取值,是因为编译器优化代码的结果(访问cpu寄存器比访问ram快的多)。volatile 指出 i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。
注意,在vc6中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码,测试有无volatile关键字,对程序最终代码的影响:
首先,用classwizard建一个win32 console工程,插入一voltest.cpp文件,输入下面的代码。
然后,在调试版本模式运行程序,输出结果如下:
i = 10
i = 32
然后,在release版本模式运行程序,输出结果如下:
i = 10
i = 10
输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。
下面,我们把 i的声明加上volatile关键字,看看有什么变化:
“`
#include
由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:
static int i=0;
int main(void)
{
…
while (1)
{
if (i) dosomething();
}
}
/* Interrupt service routine. */
void ISR_2(void)
{
i=1;
}
程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被调用。如果将将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。
一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义;
另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过有关中断来实现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。
含义
volatile总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以使代码消除。但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化,volatile的字面含义是易变的,它有下面的作用:
1、不会在两个*作之间把volatile变量缓存在寄存器中。在多任务、中断、甚至setjmp环境下,变量可能被其他的程序改变,编译器自己无法知道,volatile就是告诉编译器这种情况。
2、不做常量合并、常量传播等优化,所以像下面的代码:
volatile int i = 1;
if (i > 0) …
if的条件不会当作无条件真。
3、对volatile变量的读写不会被优化掉。如果你对一个变量赋值但后面没用到,编译器常常可以省略那个赋值*作,然而对Memory Mapped IO的处理是不能这样优化的。
前面有人说volatile可以保证对内存操作的原子性,这种说法不大准确,其一,x86需要LOCK前缀才能在SMP下保证原子性,其二,RISC根本不能对内存直接运算,要保证原子性得用别的方法,如atomic_inc。
对于jiffies,它已经声明为volatile变量,我认为直接用jiffies++就可以了,没必要用那种复杂的形式,因为那样也不能保证原子性。
你可能不知道在Pentium及后续CPU中,下面两组指令:
inc jiffies
;;
mov jiffies, %eax
inc %eax
mov %eax, jiffies
作用相同,但一条指令反而不如三条指令快。
关键在于两个地方:
1、编译器的优化
在本线程内,当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致;当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致;当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致。
举一个不太准确的例子:
发薪资时,会计每次都把员工叫来登记他们的银行卡号,一次会计为了省事,没有即时登记,用了以前登记的银行卡号,刚好一个员工的银行卡丢了,已挂失该银行卡号,从而造成该员工领不到工资。
员工 --原始变量地址
银行卡号 --原始变量在寄存器的备份
2、在什么情况下会出现
1) 并行设备的硬件寄存器(如:状态寄存器)
2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3) 多线程应用中被几个任务共享的变量
补充:volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人;
“易变”是因为外在因素引起的,象多线程,中断等,并不是因为用volatile修饰了的变量就是“易变”了,假如没有外因,即使用volatile定义,它也不会变化;
而用volatile定义之后,其实这个变量就不会因外因而变化了,可以放心使用了;大家看看前面那种解释(易变的)是不是在误导人。
volatile对应的变量可能在你的程序本身不知道的情况下发生改变,比如多线程的程序,共同访问的内存当中,多个程序都可以操纵这个变量,你自己的程序,是无法判定这个变量会发生变化;还比如,他和一个外部设备的某个状态对应,当外部设备发生操作的时候,通过驱动程序和中断事件,系统改变了这个变量的数值,而你的程序并不知道。
对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,而不会利用cache当中的原有数值,以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。
典型的例子:
for ( int i=0; i<100000; i++);
这个语句用来测试空循环的速度的
但是编译器肯定要把它优化掉,根本就不执行
如果你写成:
for ( volatile int i=0; i<100000; i++);
它就会执行了
extern:
可以置于变量或者函数前,以标识变量或者函数的定义在别的文件中,编译器遇到时在其他模块中寻找其定义。
struct:
空结构体的大小是1.为什么呢?编译器认为任何一种数据类型都有其大小,用它来定义一个变量能够分配确定大小的空间。
请求编译器尽可能的将变量存储在CPU内部寄存器,提高访问效率。补充:寄存器是一块一块小的存储空间,其存取速度比内存(大地址)要快得多,离CPU很近。register变量可能不存放在内存中,所以不能用取地址运算符
static:
修饰变量,但又分为局部变量和全局变量,但都存在内存的静态区,不过作用域不同。前者在函数体里面定义的,之鞥在这个函数里调用了,即使该函数的运行结束,其值仍不会被销毁,在下次使用时值也一样。而后者,作用域从定义之处开始,到文件结尾处结束,在定义之处的前面要想使用,也得加extern在其他文件里即使用extern也不能使用。
#include<stdio.h> static int j; void fun1(void) { static int i = 0; i++; printf("i = %4d\n",i); } void fun2(void) { j = 0; j++; printf("j = %4d\n",j); } int main() { for (int k = 0;k<10;k++) { fun1(); fun2(); } }
该代码的运行结果为:i从1加到10.而j保持1不变。
修饰函数。值对函数的作用域仅局限于本文件。C++对static赋予新的含义,下次专门开专题来写啊啦啦啦。
sizeof:
首先需要注意的是sizeof是关键字而不是函数。
#include<stdio.h> int main() { int i = 0; int j; j =sizeof int; }
此处编译会报错。因为int 之前加关键字,系统会以为是类型扩展,而sizeof i是可以编译通过的。所以说,sizeof在计算变量所占空间大小时,括号可以省略,而计算类型大小时括号不能省略。
#include<stdio.h> int b[100]; void fun(int b[100]) { int o = sizeof(b); printf("%4d\n",o); //4字节 } int main() { int *p = NULL; int j = sizeof(p); //4指针的大小为4个字节 int k = sizeof(*p); //???*p没有具体指想,大小如何确定 int a[100]; int i =sizeof(a); //400 ,此时的a代表整个数组空间 int l = sizeof(a[100]);//4 ??? int x = sizeof(&a); //400 ??? int w = sizeof(&a[0]); //4 指针四个字节 fun(b); }
signed,unsigned:
#include<stdio.h> #include<string.h> int main() { char a[1000]; int i; for (i = 0;i<1000;i++) { a[i] = -1-i; } printf("%4d\n",strlen(a)); }
答案是255.让我们来一起分析分析,for循环内,当i的值为0时,a[0]的值为-1,-1在内存里面用补码存储,为0xff,-2的补码是0xfe…当i的值为127时,a[127]的值为-128,而-128是char类型数据能表示的最小的负数。当i继续增加时,a[128]的值发生溢出,丢弃最高位,0x7f,当i继续增加到255的时候,-256的补码的低八位为0。然后当i增加到256时,-257的补码的低八位全为1即OXff.经分析,a[0]到a[254]里面的值都不为0,而a[255]为0。strlen函数是计算字符串长度的,并不包含字符串最后的’\0’.
float变量:
不能直接与0值进行比较
case:
#include<stdio.h> #include<string.h> int main() { int a=10; switch(a) { case 1: break; case 'A': break; case 1+2: break; case "A"://expression not constant break; /*case 0.1: break;//'const double' : illegal type for case expression case -0.1: break;//'const double' : illegal type for case expression case 0.1+0.9: break; case 3/2: break;//出现过了 */ } }
void:
字面意思是空类型,void*则为空类型指针,可以只想任何类型的数据。void多用于对函数参数的限定和对函数返回的限定。
不能对 void*进行算法操作。如
void *pvoid; pvoid++; pvoid+=1;
标准认为,进行算法操作符的指针必须知道其指向数据类型大小的,即必须知道内存目的地址的确切值。void不能代表一个真实的变量,它的出现是一种抽象的需要。
return:
用来终止一个函数,并返回后表表达式的值。但return语句不可返回只想“栈内存”的“指针”(如局部变量),因为该内存在函数体结束时被自动销毁。
const:
const int Max[100]; int array[Max];
在vc里分别创建.c和.cpp文件,发现在.c文件中编译器会提示出错,而在.cpp文件中则顺利运行。证明在c语言中,const修饰的是只读属性的变量,而在C++里,扩展了属性。case语句后面不可以是const修饰的只读变量。编译器通常不为普通const只读变量分配存储空间,而是将他们保存在符号表红,使得它成为一个在编译期间的值,没有了存储与读内存的操作,使得效率高。const的定义从汇编的角度来看,只是给出了对应的 内存地址,而不是#define一样给出的是立即数。所以,const定义的只读变量在程序运行时只有一份拷贝(因为他是全局的额只读变量,存放在静态区),而#define定义的变量在内存中有若干份拷贝。#define宏是在预编译阶段进行替换,而const修饰的只读变量是在编译的时候确定其值,宏没有类型,而const修饰的只读变量有特定的类型。
#define M 3//宏常量 const int N = 5;//此时N并未被放入内存中 int i = N;//此时为N分配内存,以后不再分配 int I = M;//编译期间进行宏替换,分配内存 int j = N;//没有内存分配 int J=M;//再进行宏替换,又一次分配内存
volatile:
1.编译器对访问该变量的的代码不再进行优化,从而可以提供对特殊地址的稳定访问。
比如要往某一地址送两条指令:
int *ip =...; //设备地址 *ip = 1; //第一个指令 *ip = 2; //第二个指令
以上程序compiler可能做优化而成:
int *ip = ...; *ip = 2; 结果第一个指令丢失。如果用volatile, compiler就不允许做任何的优化,从而保证程序的原意。
volatile int *ip = …;
*ip = 1;
*ip = 2;
此时,即使你要compiler做优化,它也不会把两次付值语句间化为一。它只能做其它的优化。这对device driver程序员很有用。
2.表示用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能把他放在cache或寄存器中重复使用。volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改。
用volatile关键字声明的变量i每一次被访问时,执行部件都会从i相应的内存单元中取出i的值。没有用volatile关键字声明的变量i在被访问的时候可能直接从cpu的寄存器中取值(因为之前i被访问过,也就是说之前就从内存中取出i 的值保存到某个寄存器中),之所以直接从寄存器中取值,而不去内存中取值,是因为编译器优化代码的结果(访问cpu寄存器比访问ram快的多)。volatile 指出 i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。
注意,在vc6中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码,测试有无volatile关键字,对程序最终代码的影响:
首先,用classwizard建一个win32 console工程,插入一voltest.cpp文件,输入下面的代码。
#include <stdio.h> void main() { int i=10; int a = i; printf( "i= %d/n",a); //下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道 __asm { mov dword ptr [ebp-4], 20h } int b = i; printf("i= %d/n",b); }
然后,在调试版本模式运行程序,输出结果如下:
i = 10
i = 32
然后,在release版本模式运行程序,输出结果如下:
i = 10
i = 10
输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。
下面,我们把 i的声明加上volatile关键字,看看有什么变化:
“`
#include
}
volatile的本意是“易变的”由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:
static int i=0;
int main(void)
{
…
while (1)
{
if (i) dosomething();
}
}
/* Interrupt service routine. */
void ISR_2(void)
{
i=1;
}
程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被调用。如果将将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。
一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义;
另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过有关中断来实现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。
含义
volatile总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以使代码消除。但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化,volatile的字面含义是易变的,它有下面的作用:
1、不会在两个*作之间把volatile变量缓存在寄存器中。在多任务、中断、甚至setjmp环境下,变量可能被其他的程序改变,编译器自己无法知道,volatile就是告诉编译器这种情况。
2、不做常量合并、常量传播等优化,所以像下面的代码:
volatile int i = 1;
if (i > 0) …
if的条件不会当作无条件真。
3、对volatile变量的读写不会被优化掉。如果你对一个变量赋值但后面没用到,编译器常常可以省略那个赋值*作,然而对Memory Mapped IO的处理是不能这样优化的。
前面有人说volatile可以保证对内存操作的原子性,这种说法不大准确,其一,x86需要LOCK前缀才能在SMP下保证原子性,其二,RISC根本不能对内存直接运算,要保证原子性得用别的方法,如atomic_inc。
对于jiffies,它已经声明为volatile变量,我认为直接用jiffies++就可以了,没必要用那种复杂的形式,因为那样也不能保证原子性。
你可能不知道在Pentium及后续CPU中,下面两组指令:
inc jiffies
;;
mov jiffies, %eax
inc %eax
mov %eax, jiffies
作用相同,但一条指令反而不如三条指令快。
关键在于两个地方:
1、编译器的优化
在本线程内,当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致;当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致;当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致。
举一个不太准确的例子:
发薪资时,会计每次都把员工叫来登记他们的银行卡号,一次会计为了省事,没有即时登记,用了以前登记的银行卡号,刚好一个员工的银行卡丢了,已挂失该银行卡号,从而造成该员工领不到工资。
员工 --原始变量地址
银行卡号 --原始变量在寄存器的备份
2、在什么情况下会出现
1) 并行设备的硬件寄存器(如:状态寄存器)
2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3) 多线程应用中被几个任务共享的变量
补充:volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人;
“易变”是因为外在因素引起的,象多线程,中断等,并不是因为用volatile修饰了的变量就是“易变”了,假如没有外因,即使用volatile定义,它也不会变化;
而用volatile定义之后,其实这个变量就不会因外因而变化了,可以放心使用了;大家看看前面那种解释(易变的)是不是在误导人。
volatile对应的变量可能在你的程序本身不知道的情况下发生改变,比如多线程的程序,共同访问的内存当中,多个程序都可以操纵这个变量,你自己的程序,是无法判定这个变量会发生变化;还比如,他和一个外部设备的某个状态对应,当外部设备发生操作的时候,通过驱动程序和中断事件,系统改变了这个变量的数值,而你的程序并不知道。
对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,而不会利用cache当中的原有数值,以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。
典型的例子:
for ( int i=0; i<100000; i++);
这个语句用来测试空循环的速度的
但是编译器肯定要把它优化掉,根本就不执行
如果你写成:
for ( volatile int i=0; i<100000; i++);
它就会执行了
extern:
可以置于变量或者函数前,以标识变量或者函数的定义在别的文件中,编译器遇到时在其他模块中寻找其定义。
struct:
空结构体的大小是1.为什么呢?编译器认为任何一种数据类型都有其大小,用它来定义一个变量能够分配确定大小的空间。
相关文章推荐
- c++ 桥接模式
- C++类虚函数表
- C++中引用、指针,传值的联系和区别
- 二分法查找的C++实现
- PHP中调用C/C++制作的动态链接库的教程
- 5.C语言之函数
- 我们编程吧 之 C++学习手册v0.1
- C++stringstream的clear()清空误区
- c++静态函数
- SYDZ 辗转相除法的原理与实现
- LeetCode: Find Peak Element
- C++近期常见问题面试总结(一)
- C++构造函数和析构函数的总结
- c++ 单例模式
- 高精度运算
- 常用工具类之C++线程安全集合类
- Concurrency::task(C++)
- c++第一次上机报告
- 第二次c++上机
- C++ CopyFile函数的用法