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. Linux安装配置Nginx服务器

    如有需要可以加我Q群[308742428]大家一起讨论技术,有偿服务. 后面会不定时为大家更新文章,敬请期待. 喜欢的朋友可以关注下. 前言 今天搭建nginx服务器,来访问静态资源文件. Nginx ...

  2. Docker-搭建Docker Registry

    私有Docker Registry的部署和配置 从Docker Hub上可以获取官方的Registry的镜像,Registry 默认的对外服务端口是 5000,如果我们宿主机上运行的 Registry ...

  3. 聚合函数 -AVG/MAX/MIN/STDDEV/VARIANCE/SUM/COUNT/MEDIAN

    ------------------------------------------聚合函数--------------------------------------------- --1: AVG ...

  4. 【读书笔记】C/C++程序员面试秘籍

    第一章 C/C++ 程序基础(共12题) 第二章 预处理.const.static.和 sizeof(共27题) 第三章 引用和指针(共39题) 第四章 字符串(共31题) 第五章 位运算与嵌入式编程 ...

  5. Fatal error compiling: invalid target release: 11 -> [Help 1]

    <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compi ...

  6. 使用canvas给图片添加水印, canvas转换base64,,canvas,图片,base64等转换成二进制文档流的方法,并将合成的图片上传到服务器,

    一,前端合成带水印的图片 一般来说,生成带水印的图片由后端生成,但不乏有时候需要前端来处理.当然,前端处理图片一般不建议,一方面js的处理图片的方法不全,二是有些老版本的浏览器对canvas的支持度不 ...

  7. 高级运维(四):Nginx常见问题处理、安装部署Tomcat服务器、使用Tomcat部署虚拟主机

    一.Nginx常见问题处理 目标: 本案例要求对Nginx服务器进行适当优化,以提升服务器的处理性能: 1> 不显示Nginx软件版本号 2> 如果客户端访问服务器提示“Too many ...

  8. jmeter基本问题

    jmetet加压的时候不用图像界面(GUI),直接在命令行加压(命令行生成一个report-命令行参数),不做断言,不加监听器--不然会很卡: 进入就meter命令行: 后置处理器可以从HTML页面拿 ...

  9. tomcat源码分析一之getCanonicalFile和getAbsolutePath的区别

    最近在看tomcat源码 1.getPath(): 返回定义时的路径,(就是你写什么路径,他就返回什么路径) 2.getAbsolutePath(): 返回绝对路径,但不会处理“.”和“..”的情况 ...

  10. 剑指offer——二进制中1的个数(c++)

    题目描述实现一个函数,输入一个整数,输出该数二进制表示中1的个数.例如,把9表示成二进制是1001,则输出为2 常规解法首先把n和1做位运算,判断n的最低位是不是1,然后把1左移一位得到2,再把n和2 ...