版权所有允许转载,请注明原创网址,否则追究其法律责任
原创网址:http://blog.csdn.net/night_elf_1020/archive/2008/12/06/3460715.aspx
CSDN :night_elf_1020 小武
谢谢jiahaojie的修改意见
在我的上一个专栏里,我讨论了为什么C++在数据存入ROM所使用的规则比C语言所使用的规则稍微复杂原因的其中一个。关于这个话题现在我有更多的内容想说,但是在那之前,我很乐意先回答Phil Baurer先生通过E-mail向我咨询的一个关于小松(日本一个地方)小型采矿系统的问题:
“我们正在对使用带有typedef的const的问题饶有兴趣。我希望您评论下这种用法。我正在思考是不是我们碰巧遇到了一些我们不知道的C语言规则”
“我们正在Hitachi SH-2 32位 RISC微控制器上使用Hitachi C编译器。我们认为下面的代码:
typedef void *VP;
const VP vectorTable[]
= {....}; (1)
应该等同于:
const void *vectorTable[]
= {....}; (2)”
“然而,连接器把vectorTable放在(1)中CONSTANT部分,但是(2)中方在了DATA部分。”
“这是合适的做法吗或者还是编译器的一个BUG?”
这是一个恰当的做法,不是BUG。你确实碰巧遇到了一些你显然不知道的C语言规则。不要沮丧,我的朋友,不仅仅是你这样,我相信很多其他的C和C++程序员对这些规则都很困惑,这也正是我这期专栏想要解决的问题。
我在早期专栏里提到过一些这些规则,然而,现在回顾那些文章,我认为我没有充分的解释清楚这个问题的根本所在,现在就让我再解释一次。
声明
这里是第一个观点:
在C和C++中每一个声明符都有两个基本部分组成:一串由个零个或多个字符组成的类型修饰符,以及由一个或多个声明符;中间用逗号隔开。
(Every declaration in C and C++ has two principal parts: a sequence of zero or more declaration specifiers, and a sequence of one or more declarators, separated by commas.)
例如:static unsigned long int *x[N];
declaration specifiers declarator
一个声明符就是一个被声明的名字,可能被很多操作符类似如*,[],(), and (在C++中).&连在一起。正如你已经知道的,这个符号“*”代表指针而“[]”意思是数组。这样*x[N]这个声明符就表示X是一个含有N个元素的在声明符中已经制定了的指针数组。例如:static unsigned long int *x[N];表示声明变量对象X是一个指向含有N个unsigned long int型数据的指针数组(在后面将解释关键词static并不在对类型起作用)。
我们怎么知道*x[N]是一个数组指针还是一个指针数组哪?有以下规则:对声明符的操作顺序取决于的修饰符在一个表达式中表现的优先级。
例如,如果你参照下最近无论是C还是C++的优先级列表,你都将看到[]比*优先级高,这样在声明*X[N]中X首先是一个数组,其次它是一个指针。
圆括号在这个声明符中有两种作用:首先,函数调用操作,()与[]有相同的优先级;在分组时,()拥有最高的优先级。例如:*f(int)声明f是一个函数,返回指针类型。相反(*f)(int)是一个指向函数的指针。一个声明符通常包含不止一个标志符。这个声明*x[N]包含两个标志符,x和N。仅仅一个标识符是需要被声明的,其他的必须在前面声明。也就是说x[N]声明的标志符是x。.
一个声明符没有必要包含太多的操作。在一个声明中:int n;没有做除声明n意外的任何操作。
声明指定
一些声明修饰符可以指定一些声明符为int、unsigned等类型,或者一个新类型的标志符,也可以被声明为某个如extern或者static的类型。C++中也可以用inline或者virtual来修饰声明函数标志符。这里有这样一个观点:
类型修饰符为标志符类型的标志ID,其他的修饰符提供了非类型信息但是却直接对标志ID有用。
例如:static unsigned long int *x[N];
声明符x是一个含有N个unsigned long int数据的指针数组,关键词static表明C只有静态分配内存。在你信中提到的例子另我怀疑你可能正在被这样一个事实迷惑:关键词const和valatile是类型修饰符。例如,const在(2)中,并没有直接修饰vectorTable,而是修饰的void。这表明vectorTable作为一个const void类型的指针数组,似乎你希望它是指向void的一个数组常指针。
这里还有一个重要的观点:在声明修饰符中的顺序与声明的顺序不匹配。
例如:const VP vectorTable[]等同于VP const vectorTable[],而const void *vectorTable[]等同于void const *vectorTable[]。
我们大都把存储器类声明符比如static放在声明符第一位(最左边),但是仅仅是通常情况下,不是语言的需要。声明符const和volatile与此不同:不仅仅能出现在声明修饰符中,也能用作声明符。例如const在void *const vectorTable[]作为声明符,这种情况下你不能确定关键词的顺序,例如*const void vectorTable[]就是个错误的例子。
一种提倡的代码风格
正如我前面所解释的,声明修饰符的顺序取决于编译器,而与声明的顺序无关,因此const void *vectorTable[] (3)和void const *vectorTable[](4)等价。几乎所有的C和C++程序员更喜欢类似(3)式写const和volatile在表达式的最左边,而我更喜欢写它们在类似(4)的最右边,并且强烈推荐这种方式。
尽管C和C++大都是从顶向下和从左自右,然而指针声明符一定意义上说是逆着来。也就是说指针声明符是从右向左的,并且使const出现在最右边。例如声明T const *p;声明了一个指向常T类型的指针,而T *const p;声明了一个指向T类型的常指针。把const写在其他声明符的右边能使const限制符的作用更容易分辨。使用原来在信中的例子:typedef void *VP;
const VP vectorTable[]。
一种解释是替换VP:
const VP vectorTable[]
const void *vectorTable[]
这似乎使得vectorTable是指向一个常void类型指针数组的意义显得更明显。但是这是错误的。正确的理解是替换VP为
const VP vectorTable[]
void *const vectorTable[]
这样,vectorTable一个指向空类型常指针数组,但是一点也不明显。
把const写在声明符的最右边能使它更容易解释:
VP const vectorTable[]
void *const vectorTable[]
现在,我意识到我在建议一种很少人用的风格。仅仅针对每一个用const的在左面的使用者。然而,究竟会有多少C和C++程序员明白他们在使用const的地方到底在做什么。每个人都不这样做是对现在流行的风格的支持的一个争论。只要我还在从事这个行业,我就会义无反顾的支持这种风格。虽然很多C程序员对此没有任何影响,但是许多c++程序员却不幸的养成了这样一个书写习惯。
const int* p;
而不是
const int *p;
这就是说,他们把空格和*作为声明修饰符一起使用而不是声明符。用这种风格写代码的话,我真的相信C++程序员这么做并且互相制造麻烦。的确,空格的位置对编译器来说没有任何区别,但是把空格放在*后面一会造成一种错误的表象在理解声明符的时候。意识到在最后声明修饰符和声明符的分界点是理解声明符一个重要关键之一。用空格分开只能令人产生迷惑的情形。
我希望我的回答能令你满意并且澄清了你的疑惑。