




已阅读5页,还剩2页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1.引言C+语言的创建初衷是“a better C”,但是这并不意味着C+中类似C语言的全局变量和函数所采用的编译和连接方式与C语言完全相同。作为一种欲与C兼容的语言,C+保留了一部分过程式语言的特点(被世人称为“不彻底地面向对象”),因而它可以定义不属于任何类的全局变量和函数。但是,C+毕竟是一种面向对象的程序设计语言,为了支持函数的重载,C+对全局函数的处理方式与C有明显的不同。2.从标准头文件说起某企业曾经给出如下的一道面试题:为什么标准头文件都有类似以下的结构?#ifndef _INCvxWorksh#define _INCvxWorksh#ifdef _cplusplusextern C #endif/*.*/#ifdef _cplusplus#endif#endif /* _INCvxWorksh */显然,头文件中的编译宏“#ifndef _INCvxWorksh、#define _INCvxWorksh、#endif” 的作用是防止该头文件被重复引用。那么#ifdef _cplusplusextern C #endif#ifdef _cplusplus#endif的作用又是什么呢?我们将在下文一一道来。3.深层揭密extern Cextern C 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;其次,被它修饰的目标是“C”的。让我们来详细解读这两重含义。被extern C限定的函数或变量是extern类型的;extern是C/C+语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。记住,下列语句:extern int a;仅仅是一个变量的声明,其并不是在定义变量a,并未为a分配内存空间。变量a在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数。与extern对应的关键字是static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰。被extern C修饰的变量和函数是按照C语言方式编译和连接的;未加extern “C”声明时的编译方式首先看看C+中对类似C的函数是怎样编译的。作为一种面向对象的语言,C+支持函数重载,而过程式语言C则不支持。函数被C+编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:void foo( int x, int y );该函数被C编译器编译后在符号库中的名字为_foo,而C+编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。_foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C+就是靠这种机制来实现函数重载的。例如,在C+中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。同样地,C+中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以.来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。未加extern C声明时的连接方式假设在C+中,模块A的头文件如下:/ 模块A头文件moduleA.h#ifndef MODULE_A_H#define MODULE_A_Hint foo( int x, int y );#endif在模块B中引用该函数:/ 模块B实现文件moduleB.cpp#include moduleA.hfoo(2,3);实际上,在连接阶段,连接器会从模块A生成的目标文件moduleA.obj中寻找_foo_int_int这样的符号!加extern C声明后的编译和连接方式加extern C声明后,模块A的头文件变为:/ 模块A头文件moduleA.h#ifndef MODULE_A_H#define MODULE_A_Hextern C int foo( int x, int y );#endif在模块B的实现文件中仍然调用foo( 2,3 ),其结果是:(1)模块A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;(2)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo。如果在模块A中函数声明了foo为extern C类型,而模块B中包含的是extern int foo( int x, int y ) ,则模块B找不到模块A中的函数;反之亦然。所以,可以用一句话概括extern “C”这个声明的真实目的(任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题):实现C+与C及其它语言的混合编程。明白了C+中extern C的设立动机,我们下面来具体分析extern C通常的使用技巧。4.extern C的惯用法(1)在C+中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:extern C#include cExample.h而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern C声明,在.c文件中包含了extern C时会出现编译语法错误。笔者编写的C+引用C函数例子工程中包含的三个文件的源代码如下:/* c语言头文件:cExample.h */#ifndef C_EXAMPLE_H#define C_EXAMPLE_Hextern int add(int x,int y);#endif/* c语言实现文件:cExample.c */#include cExample.hint add( int x, int y )return x + y;/ c+实现文件,调用add:cppFile.cppextern C#include cExample.hint main(int argc, char* argv)add(2,3);return 0;如果C+调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern C 。(2)在C中引用C+语言中的函数和变量时,C+的头文件需添加extern C,但是在C语言中不能直接引用声明了extern C的该头文件,应该仅将C文件中将C+中定义的extern C函数声明为extern类型。笔者编写的C引用C+函数例子工程中包含的三个文件的源代码如下:/C+头文件 cppExample.h#ifndef CPP_EXAMPLE_H#define CPP_EXAMPLE_Hextern C int add( int x, int y );#endif/C+实现文件 cppExample.cpp#include cppExample.hint add( int x, int y )return x + y;/* C实现文件 cFile.c/* 这样会编译出错:#include cExample.h */extern int add( int x, int y );int main( int argc, char* argv )add( 2, 3 );return 0;如果深入理解了第3节中所阐述的extern C在编译和连接阶段发挥的作用,就能真正理解本节所阐述的从C+引用C函数和C引用C+函数的惯用法。对第4节给出的示例代码,需要特别留意各个细节。内核中有这样一个#define GPFCON (*(volatile unsigned *)0x56000050)#define(*(volatile unsigned *) ) 讲解对于(volatile unsigned char *)0x20我们再分析一下,它是由两部分组成:1)(unsigned char *)0x20,0x20只是个值,前面加(unsigned char *)表示0x20是个地址,而且这个地址类型是unsigned char ,意思是说读写这个地址时,要写进unsigned char 的值,读出也是unsigned char 。2)volatile,关键字 volatile 确保本条指令不会因C 编译器的优化而被省略,且要求每次直接读值。例如用while((unsigned char *)0x20)时,有时系统可能不真正去读0x20的值,而是用第一次读出的值,如果这样,那这个循环可能是个死循环。用了volatile 则要求每次都去读0x20的实际值。那么(volatile unsigned char *)0x20是一个固定的指针,是不可变的,不是变量。而char *u则是个指针变量。再在前面加*:*(volatile unsigned char *)0x20则变成了变量(普通的unsigned char变量,不是指针变量),如果#define i (*(volatile unsigned char *)0x20),那么与unsigned char i是一样了,只不过前面的i的地址是固定的。那么你的问题就可解答了,(*(volatile unsigned char *)0x20)可看作是一个普通变量,这个变量有固定的地址,指向0x20。而0x20只是个常量,不是指针更不是变量。关于volatile关键字这个多是嵌入式编程时可能会用到。volatile的意思是告诉编译器,在编程源代码时,对这个变量不要使用优化。在一般的程序设计中,如:int *a; int b;b = (*a) * (*a);这种情况。通常编译器为了减少存储器的读写时间,会把代码优化为:int *a; int b; int c;c = *a;b = c * c;因为外部存储器的读写速度肯定赶不上内存的读写速度,这样可以省一次外部存储器的读取时间,从而提高速度。如果把int *a改为volatile int* a编译器就不会自动把它优化掉了。在整个运算过程中,对变量*a的值读取了再次。防止因变量*a的值在这一期间发生了改变,而导致程序结果的错误。 以 #define IOPIN (*(volatile unsigned long *) 0xE0028000) 为例:作为一个宏定义语句,define是定义一个变量或常量的伪指令。首先( volatile unsigned long * )的意思是将后面的那个地址强制转换成 volatile unsigned long * ,unsigned long * 是无符号长整形,volatile 是一个类型限定符,如const一样,当使用volatile限定时,表示这个变量是依赖系统实现的,以为着这个变量会被其他程序或者计算机硬件修改,由于地址依赖于硬件,volatile就表示他的值会依赖于硬件。volatile 类型是这样的,其数据确实可能在未知的情况下发生变化。比如,硬件设备的终端更改了它,现在硬件设备往往也有自己的私有内存地址,比如显存,他们一般是通过映象的方式,反映到一段特定的内存地址当中,这样,在某些条件下,程序就可以直接访问这些私有内存了。另外,比如共享的内存地址,多个程序都对它操作的时候。你的程序并不知道,这个内存何时被改变了。如果不加这个voliatile修饰,程序是利用catch当中的数据,那个可能是过时的了,加了 voliatile,就在需要用的时候,程序重新去那个地址去提取,保证是最新的。归纳起来如下:1. volatile变量可变允许除了程序之外的比如硬件来修改他的内容2. 访问该数据任何时候都会直接访问该地址处内容,即通过cache提高访问速度的优化被取消对于(volatile unsigned long *) 0xE0028000)为随硬件需
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 蒸汽包安全知识培训总结课件
- 2025年安全员C证考试题库(含答案)
- 小班常识教学题目及答案
- 线段的判断题目及答案
- 显著性检验题目及答案
- 沧州十四中一模考试题及答案
- 物业保安证试题及答案
- 2025年4月中药药剂学模拟考试题与参考答案
- 2025年化工自动化控制仪表考试试题题库
- 2025年超细铜粉项目建议书
- 神经外科手术机器人辅助脑干出血穿刺引流术专家共识解读
- 2025年吴忠市公安局招聘警务辅助人员招聘考试笔试试题(含答案)
- 代建工作管理办法
- 《剪映短视频剪辑+即梦AI从入门到精通》AI短视频生成与剪辑全套教学课件
- 民建支部管理办法
- 2025年院感知识理论考试题库及答案
- 科技伦理审查管理办法
- 中学教学常规管理汇报
- 胸部损伤外科诊疗体系
- 土石方工程计量计价课件
- 第27课 中国特色社会主义的开创与发展 课件 中外历史纲要(上)
评论
0/150
提交评论