Dan Saks_const T vs T const

const用法精髓
原文地址: Dan Saks_const vs const 作者: 梧桐

In my last column, I discussed one of the reasons why the rules by which a compiler can place data into ROM are a bit more complicated in C++ than they are in C. I have more to say about that subject, but before I do, I’d like to reply to the following query I received through e-mail from Phil Baurer at Komatsu Mining Systems:

 

" We’re having an interesting problem using const with a typedef. I hoped you could comment on this situation. I am wondering if we are bumping into some unknown (by us) rule of the C language."

 

" We are using the Hitachi C compiler for the Hitachi SH-2 32-bit RISC microcontroller. We thought the following code:

 

typedef void *VP;
const VP vectorTable[] = {..<data>..};
                               (1)

 

should be identical to:


const void *vectorTable[] = {..<data>..};                          (2)

 

"However, the linker places vectorTable in (1) into the CONSTANT section, but it places vectorTable in (2) into the DATA section.Is this the proper behavior or a bug in the compiler?”

This is proper behavior; it is not a bug. You are indeed bumping into some rules of the C language that you apparently don’t know about. Don’t feel bad; you’re not alone. I believe many other C and C++ programmers are confused about these rules, which is why I’m answering this in my column.

 

I presented some of these rules in an earlier column. However, in looking back at that column, I don’t think I emphasized strongly enough the points which seem to be the source of your confusion. So let me try again.

 

Although C and C++ read mostly from top-to-bottom and left-to-right, pointer declarations read, in a sense, backwards.

 

Declarators
Here’s the first insight:


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.

 

For example:

static unsigned long int *x[N];

 

static unsigned long int declaration specifiers
*x[N]                              declarator
 

 A declarator is the name being declared, possibly surrounded by operators such as *, [], (), and (in the case of C++) &. As you already know,the symbol * in a declarator means “pointer to” and [] means “array of.” Thus, *x[N] is a declarator indicating that x is an “array of N elements of pointer to ...” something, where that something is the type specified in the declaration specifiers. For example,

 

static unsigned long int *x[N];


declares x as an object of type “array of N elements of pointer to unsigned long int.” (As explained later, the keyword static does not contribute to the type.) How did I know that *x[N] is an “array of ... pointer to ...” rather than a “pointer to an array of ...?” It follows from this rule:

 

The operators in a declarator group according to the same precedence as they do when they appear in an expression.

For example, if you check the nearest precedence chart for either C or C++, you’ll see that [] has higher precedence than *. Thus the declarator *x[N] means that x is an array before it’s a pointer. Parentheses serve two roles in declarators: first, as the function call operator, and second, as grouping. As the function call operator, () have the
same precedence as []. As grouping, () have the highest precedence of all.

Most of us place storage class specifiers such as static as the first (leftmost) declaration specifier, but it’s just a common convention, not a language requirement.

 

For example, *f(int) is a declarator specifying that f is a “function ... returning a pointer ... .” In contrast, (*f)(int) specifies that f is a “pointer to a function ... .”

 

A declarator may contain more than one identifier. The declarator *x[N] contains two identifiers, x and N. Only one of those identifiers is the one being declared, and it’s called the declarator-id. The other(s), if any, must have been declared previously. For instance, the declarator-id in *x[N] is x.

A declarator need not contain any operators at all. In a declaration as simple as:

int n;

the declarator is just the identifier n without any operators.

 


Declaration specifiers

 

Some of the declaration specifiers leading up to a declarator can be type specifiers such as int, unsigned, or an identifier that names a type. They can also be storage class specifiers such as extern or static. In C++ they can also
be function specifiers such as inline or virtual.

Here’s another insight:
Type specifiers contribute to the type of the declarator-id; other specifiers provide non-type information that applies directly to the declarator-id.

 

For example:
static unsigned long int *x[N];

declares x as a variable of type “array of N elements of type pointer to unsigned long int.” The keyword static specifies that x has statically allocated storage.

 The examples in your letter lead me to suspect that you may have been tripped up by the fact that: The keywords const and volatile are type specifiers.


For example, the const in:
const void *vectorTable[] = {..<data>..};               (2)

 

does not apply directly to vectorTable; it applies directly to void. This declaration declares vectorTable as a variable of type “array of pointer to const void.” It appears that you were expecting it to be “const array of pointer to void.”

Here’s yet another important insight:
The order in which the declaration specifiers appear in a declaration doesn’t matter.

 

Thus, for example,
const VP vectorTable[]

is equivalent to:
VP const vectorTable[]

and

const void *vectorTable[]

is equivalent to:
void const *vectorTable[]

 

Most of us place storage class specifiers such as static as the first (leftmost) declaration specifier, but it’s just a common convention, not a language requirement.

The declaration specifiers const and volatile are unusual in that:

 

The only declaration specifiers that can also appear in declarators are const and volatile.

 

For example, the const in:

void *const vectorTable[]

appears in the declarator. In this case, you cannot rearrange the order of the keywords. For example:
*const void vectorTable[]
is an error.

A clarifying style

 

As I explained earlier, the order of the declaration specifiers doesn’t matter to the compiler. Therefore, these declarations are equivalent:

const void *vectorTable[]          (3)
void const *vectorTable[]          (4)

 

Almost all C and C++ programmers prefer to write const and volatile to the left of the other type specifiers, as in (3). I prefer to write const and volatile to the right, as in (4), and I recommend it. Strongly.

Although C and C++ read mostly from top-to-bottom and left-to-right, pointer declarations read, in a sense, backwards. That is, pointer declarations read from right-to-left. By placing const to the right of the other type specifiers, you can read pointer declarations strictly from right-to-left and get const to come out in the “right” places. For example:

 

T const *p;
declares p as a “pointer to a const T,” which is exactly what it is. Also:

T *const p;
declares p as a “const pointer to a T,” which is also the correct interpretation.

 

Recognizing the boundary between the last declaration specifier and the
declarator is one of the keys to understanding declarations.

 

Writing const to the right of the other declaration specifiers actually makes it easier to see the effect of combining const with a typedef name. Using the original example in the letter:

typedef void *VP;
const VP vectorTable[]

 

One interpretation is to replace VP as follows:

const VP vectorTable[]
const void *vectorTable[]

 

which makes it appear that vectorTable has type “array of pointer to const void.” This is wrong! The correct interpretation is to replace VP as:

const VP vectorTable[]
void *const vectorTable[]

 

That is, vectorTable type “array of const pointer to void,” but it’s not at all obvious.
Writing const as the rightmost declaration specifier makes it easier to see the correct interpretation:


VP const vectorTable[]
void *const vectorTable[]

 

Now, I realize that I’m recommending a style that hardly anyone uses. Just about everyone who uses const places it to the left. However, given how few C and C++ programmers really understand what they’re doing when it comes to using const in declarations, “everyone else does it” is hardly an argument in favor of the currently popular style. Why not buck the trend and try using a clearer style?


As long as I’m on a roll here, I might as well get in my digs in on a related style point. Although most C programmers seem to have remained unsullied by this, many C++ programmers have acquired the most unfortunate habit of writing:

const int* p;

 

rather than:
const int *p;

 

That is, they use spacing to join the * with the declaration specifiers rather than with the declarator. I really believe C++ programmers do themselves and each other a disservice when they write declarations in this style. Sure, the spacing makes no difference to the compiler, but putting the space after the * leaves many people with a false impression about the underlying structure of declarations.Recognizing the boundary between the last declaration specifier and the declarator is one of the keys to understanding declarations. Breaking up declarators with spaces this way only confuses the situation.

I hope I’ve answered your question and clarified some issues.

 

 

在我的上一篇文章中,我讨论了为什么C++中编译器把数据存入ROM的规则比C中的更复杂。关于这个主题,我还想再谈谈,不过在此之前,我想先回答下面这个问题,这个问题是来自Komatsu Mining SystemsPhil Baurer发来的邮件:

 

我们现在在使用typedef const的时候遇到了一个有趣的问题,我希望你能评论一下这种情况,我想知道我们是否碰到了C语言的某种不明规则。我们使用的是Hitachi SH-2 32-bit RISC 微控制器的Hitachi C 编译器。我们认为下面的代码:

 

typedef void *VP;
const VP vectorTable[] = {..<data>..};                               (1)

应该和下面的相同:


const void *vectorTable[] = {..<data>..};                          (2)


甚至,连接器把(1)中的vectorTable放在CONSTANT域,而把(2)中的vectorTable放在DATA域。这是正确的还是编译器中的错误?

 

这种做法是正确的,并不是错误,你的确碰到了一些你不知道的C语言的规则。不要灰心,你不是唯一一个,我相信很多其他CC++程序员也对此感到困惑,这也是为什么我要在我的文章中回答这个问题。

 

我在早期的文章中提过其中的一些规则,但是回顾起来,我觉得,对那些造成你困惑的知识点我强调得并不够,因此我再强调一下。


尽管CC++基本上是按从头到尾、从左到右的顺序来读,但是指针的声明,从某种意义来讲却是倒着的

 

声明

这是第一个观点:


C
C++中的每个声明都有两个主要部分:零个或者更多声明说明符,和一个以上用逗号隔开的声明符

 

例如:

static unsigned long int *x[N];

 

static unsigned long int 声明说明符
*x[N]                              :声明符
 

 一个声明符就是被声明的名称,可能伴有操作符,比如 *, [], (), (C++中的) &. 正如你所知的,声明中的符号*表示指针[]表示 序列。因此, *x[N]表明x 是一个N个指针元素的序列,分别指向某某,某某就是声明中指定的类型。例如,

 

static unsigned long int *x[N];


x声明为指向unsigned long intN个指针元素的序列的一个对象。(后面会解释,关键词static对这种类型没有意义。)为什么我知道*x[N]是一个指针的序列,而不是指向一个序列的指针? 它遵循以下规则:

 

在一个表达式中,声明符中的操作符根据他们的优先级来分组

 

例如,在CC++中,如果检查最近优先级图表,你会发现[]的优先级比*更高。因此声明符*x[N]表明x是一个优先于指针的序列。圆括号在声明符中有两个作用:第一,作为函数调用的操作符,第二,用来分组。作为函数调用的操作符,()[]的优先级相同。用作分组时,()的优先级是最高的。

 

大多数人把存储类说明符(如static)作为第一个(最左边的)声明说明符,但是它只是一个惯例,并非是语言要求。

 

例如,*f(int)表示f是一个函数,返回一个指针。相反,(*f)(int)表示f是一个指向函数的指针。

 

一个声明符可能包含不止一个标识符。声明符*x[N] 包含两个标识符,xN。只有其中一个标识符是被声明的,而且被称为是声明符ID,其余的必须在这之前就被声明过。举例,*x[N] 中的声明符IDx

 

一个声明符可以不包含任何操作符。可以很简单,如:

int n;

这个声明符只有标识符n,没有任何操作符。

 

声明说明符

 

一个声明符可以是类型说明符,如int,unsigned,或者类型名称的标识符。他们也可以是存储类说明符,如externstatic。在C++中,他们也可以是函数说明符,如inlinevirtual

 

这里有另一个观点:

类型说明符表明声明符ID的类型;其他说明符提供直接适用于这个声明符ID的一些类型无关的信息。

 

举例:

static unsigned long int *x[N];

 

声明x的类型是指向unsigned long intN个指针元素的序列。关键词static表明x有静态分配的存储空间。

 

你信中的这些例子使我怀疑你可能被这样一个事实困住:const关键词和volat都是类型说明符。

 

 

例如,下面声明中的const

const void *vectorTable[] = {..<data>..};               (2)

 

没有直接适用于vectorTable,而是适用于void。这句声明表示vectorTable的类型是指向const void的指针序列。但是明显你希望它是指向void的指针的const序列

 

有另外一个重要的观点:

声明说明符在一个声明中出现的顺序并不重要。

 

所以,比如:

const VP vectorTable[]

等同于:

VP const vectorTable[]

 

还有,

const void *vectorTable[]

等同于:
void const *vectorTable[]

大多数人把存储类说明符(如static)作为第一个(最左边的)声明说明符,但是它只是一个惯例,并非是语言要求。

 

声明说明符constvolatile不寻常,因为:

能出现在声明符中的声明说明符只有constvolatie

 

例如,下句中的const

void *const vectorTable[]

出现在声明符中。这种情况,你不能重排关键词的顺序。比如:

*const void vectorTable[]

是错误的。

 

声明风格

 

正如我之前解释的一样,声明说明符的顺序对编译器来说并不重要。所以,下面的声明是等同的:

const void *vectorTable[]          (3)
void const *vectorTable[]          (4)

 

大多数CC++程序员更喜欢把constvolatile写在其他类型的说明符的左边,同(3)。而我更喜欢把constvolatile写在右边,如(4),而且强烈推荐这样写。

 

C++基本上是按从头到尾、从左到右的顺序来读,但是指针的声明,从某种意义来讲却是倒着的。指针的声明是从右到左来看。把const放在其他类型说明符的右边,可以严格的从右到左来看指针声明,还可以把const右边的位置提出来,如:

 

T const *p;

p声明为指向const T 的指针,非常准确,同样:

 

T *const p;

p声明为指向Tconst指针,也能正确的理解。

 

const写在其他声明说明符的右边,实际上可以更容易的看出const和类型名称相结合的效果。用信中的原始例子:

 

typedef void *VP;
const VP vectorTable[]

 

一种解释是把VP按下面的方法重新放置:

const VP vectorTable[]
const void *vectorTable[]

显然vectorTable的类型是指向const void 的指针序列,这是错误的! 正确的理解应该是按下面的方法:

const VP vectorTable[]
void *const vectorTable[]

 

vectorTable的类型是指向voidconst指针的序列,但是并不明显。把const写在最右边更容易理解:

VP const vectorTable[]
void *const vectorTable[] 

 

 

现在,我意识到自己正在推荐一种几乎没人使用的风格。只是因为每个人都把const放在左边。甚至,太少的CC++程序员真正明白他们在声明中使用const的意义,其他人都这么做,这句话对支持这种流行风格的人来说几乎是没有非议。为什么不抵制这种趋势来尝试更清晰的风格?

 

 既然写到这里,我也谈一下相关的风格。尽管大多数C程序员在这方面看起来保留着清白,但是很多C++程序员已经养成了最不好的书写习惯:

 

const int* p;

而不是

const int *p;

他们用空格把*和声明说明符放在一起,而不是把*和声明符放在一起。我真的认为C++程序员写这种风格的声明是在害人害己。当然,空格对编译器没有影响,但是把空格放在*后面会给人留下对声明结构的错误印象。分清前一个声明说明符和声明符的边界是理解声明的关键之处。用空格打破声明符只会造成混乱。

 

我希望我已经回答了你的问题,也阐明了一些问题。

 from the internet

zz:http://blog.csdn.net/bianbian17556231/article/details/5398276

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值