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.

Saks就const解释的更多相关文章

  1. c++ 字符串函数用法举例

    1. substr() 2. replace() 例子:split() 字符串切割: substr 函数原型: , size_t n = npos ) const; 解释:抽取字符串中从pos(默认为 ...

  2. IOS试题收集1

    IOS试题收集1 1.Objective C中有多继承吗?没有的话用什么代替? Protocol 2.Objective C中有私有方法吗?私有变量呢? OC类里面只有静态方法和实例方法这两种,@pr ...

  3. C语言声明解析方法

    1.C语言声明的单独语法成份     声明器是C语言声明的非常重要成份,他是所有声明的核心内容,简单的说:声明器就是标识符以及与它组合在一起的任何指针.函数括号.数组下表等,为了方便起见这里进行分类表 ...

  4. c专家编程---优先级规则

    对于一些复杂的类型组合,总是搞不明白,今天阅读了“优先级规则”这块,有了进一步的理解,特将规则记在此处,供自己学习查询使用. 优先级规则: A.声明从它的名字开始读取,然后按照优先级顺序依次读取 B. ...

  5. 回首C语言关键字(~回首向来萧瑟处~)

    开篇废话: 本文意在回顾 C 语言中的关键字,整理文件发现当时做的这些笔记还是蛮用心的,有临摹 前辈的足迹也有自己的理解和体会.时至今日2018已经跨过一半,对不起过去半年,今天 拿这篇关键字开篇,开 ...

  6. C核心 那些个关键字

    概述 - C语言老了 目前而言(2017年5月12日) C语言中有 32 + 5 + 7 = 44 个关键字. 具体如下 O(∩_∩)O哈哈~ -> C89关键字 char short int ...

  7. c/c++排坑(5) -- c语言中的申明

    C语言的申明总是令人头大,对于这块内容也一直让我头疼.希望通过这篇博客能够稍微梳理一下.材料和例子来源于<C专家编程> 一.C语言的申明的优先级规则 先来个例子,看看下面这行C代码到底是个 ...

  8. 《C程序设计语言》笔记(二)

    四:函数与程序结构 1:函数之间的通信可以通过参数.函数返回值以及外部变量进行. 2:如果函数定义中省略了返回值类型,则默认为int类型.如果没有函数原型,则函数将在第一次出现的表达式中被隐式声明,比 ...

  9. C语言基础知识(三)——指针

    指针定义 1.指针的值表示的是它所指向对象的地址,指针+1表示的是下一元素的地址,按**字节**编址,而不是下一字节的地址. 2.依照数据类型而定,short占用两字节.int占用4字节.double ...

随机推荐

  1. Android中使用VideoView 播放视频

    VideoView一般结合MediaController类使用,它会提供一个友好的图形界面,通过该界面可以控制视频的播放 package com.test.videoview; import andr ...

  2. 服务bindService()方法启动服务

    public class MainActivity extends Activity { private EditText studentno; private ServiceConnection c ...

  3. 深入理解java虚拟机JVM(下)

    深入理解java虚拟机JVM(下) 链接:https://pan.baidu.com/s/1c6pZjLeMQqc9t-OXvUM66w 提取码:uwak 复制这段内容后打开百度网盘手机App,操作更 ...

  4. MHA-Atlas-MySQL高可用(上)

    MHA-Atlas-MySQL高可用(上) 链接:https://pan.baidu.com/s/17Av92KQnJ81Gc0EmxSO7gA 提取码:a8mq 复制这段内容后打开百度网盘手机App ...

  5. 【Luogu】【关卡2-1】简单的模拟(2017年10月)

    任务说明:开始普及组的训练!所谓模拟,就是直接根据题意编写,思维难度简单. 铺地毯 进制转换 多项式输出 机器翻译 排座椅 笨小猴 都是简单模拟题  

  6. 我的浏览器标签同步方案:坚果云+Floccus

    前言 floccus github地址: https://github.com/marcelklehr/floccus Floccus插件是一款浏览器书签收藏同步插件,支持Chrome和Firefox ...

  7. openFrameworks Download

    { https://openframeworks.cc/zh_cn//download/ } 0.10.1 是最新发布的版本. 这个版本是修改了一些BUG的小版本,与版本 0.10.1100%兼容而且 ...

  8. Magento开启模板路径提示

    Magento的模板就好像搭积木一样,一个一个区块累加为一层,一层一层嵌套为一个整体,看起来结构相当复杂.虽然大部分模板文件路径在page.xml等文件中能找到,但是还是有部分是系统自带的.在上面并没 ...

  9. Centos操作命令

    查看已经开放的端口:firewall-cmd --list-ports 开启端口:firewall-cmd --zone=public --add-port=80/tcp --permanent 重新 ...

  10. SP6779 GSS7 - Can you answer these queries VII

    纯数据结构题,没有思维难度.直接用线段树求最大子段和的方法完成树上路径的合并.注意链上合并顺序要符合序列的前后顺序. #include <cstdio> #include <cstr ...