Saks就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.
Saks就const解释的更多相关文章
- c++ 字符串函数用法举例
1. substr() 2. replace() 例子:split() 字符串切割: substr 函数原型: , size_t n = npos ) const; 解释:抽取字符串中从pos(默认为 ...
- IOS试题收集1
IOS试题收集1 1.Objective C中有多继承吗?没有的话用什么代替? Protocol 2.Objective C中有私有方法吗?私有变量呢? OC类里面只有静态方法和实例方法这两种,@pr ...
- C语言声明解析方法
1.C语言声明的单独语法成份 声明器是C语言声明的非常重要成份,他是所有声明的核心内容,简单的说:声明器就是标识符以及与它组合在一起的任何指针.函数括号.数组下表等,为了方便起见这里进行分类表 ...
- c专家编程---优先级规则
对于一些复杂的类型组合,总是搞不明白,今天阅读了“优先级规则”这块,有了进一步的理解,特将规则记在此处,供自己学习查询使用. 优先级规则: A.声明从它的名字开始读取,然后按照优先级顺序依次读取 B. ...
- 回首C语言关键字(~回首向来萧瑟处~)
开篇废话: 本文意在回顾 C 语言中的关键字,整理文件发现当时做的这些笔记还是蛮用心的,有临摹 前辈的足迹也有自己的理解和体会.时至今日2018已经跨过一半,对不起过去半年,今天 拿这篇关键字开篇,开 ...
- C核心 那些个关键字
概述 - C语言老了 目前而言(2017年5月12日) C语言中有 32 + 5 + 7 = 44 个关键字. 具体如下 O(∩_∩)O哈哈~ -> C89关键字 char short int ...
- c/c++排坑(5) -- c语言中的申明
C语言的申明总是令人头大,对于这块内容也一直让我头疼.希望通过这篇博客能够稍微梳理一下.材料和例子来源于<C专家编程> 一.C语言的申明的优先级规则 先来个例子,看看下面这行C代码到底是个 ...
- 《C程序设计语言》笔记(二)
四:函数与程序结构 1:函数之间的通信可以通过参数.函数返回值以及外部变量进行. 2:如果函数定义中省略了返回值类型,则默认为int类型.如果没有函数原型,则函数将在第一次出现的表达式中被隐式声明,比 ...
- C语言基础知识(三)——指针
指针定义 1.指针的值表示的是它所指向对象的地址,指针+1表示的是下一元素的地址,按**字节**编址,而不是下一字节的地址. 2.依照数据类型而定,short占用两字节.int占用4字节.double ...
随机推荐
- HTML CSS的中英文对照
python 大蟒蛇 downloads 下载 install 安装 customize 自定义 path 环境变量:路径 optional 可选的 feature 特性特点 documentatio ...
- mysql-视图及索引简介
一.视图的创建.作用及注意事项 1.创建:create view 视图名 as select 语句: 2.删除:drop view 视图名 3.作用: 数据库视图允许简化复杂查询 数据库视图有助于限制 ...
- shell date 格式化
https://www.tutorialkart.com/bash-shell-scripting/bash-date-format-options-examples/ DATE=`date '+%d ...
- PHP-缺失的第一个正数
给定一个未排序的整数数组,找出其中没有出现的最小的正整数. 示例 1: 输入: [1,2,0]输出: 3示例 2: 输入: [3,4,-1,1]输出: 2示例 3: 输入: [7,8,9,11,12] ...
- Python爬虫总结——常见的报错、问题及解决方案
在爬虫开发时,我们时常会遇到各种BUG各种问题,下面是我初步汇总的一些报错和解决方案. 在以后的学习中,如果遇到其他问题,我也会在这里进行更新. 各位如有什么补充,欢迎评论区留言~~~ 问题: IP被 ...
- windows10 注销 锁定
锁定,暂时离开电脑时使用. 跑程序,下载内容(注意有时要修改一些软件的设置)继续进行. 锁定电脑,这时就不要关机. 注销快于重启. 一个账号 后台跑程序 https://zhidao.baidu.co ...
- TCP协议解析及相关问题
TCP协议是什么: TCP是一种传输控制层的协议(TCP,Transmission Control Protocol)是为了在不可靠的互联网络上提供可靠的端到端字节流而专门设计的一个传输协议.也就是要 ...
- 【leetcode】133. Clone Graph
题目如下: Given the head of a graph, return a deep copy (clone) of the graph. Each node in the graph con ...
- Vue学习笔记【6】——事件修饰符
.stop 阻止冒泡(阻止事件向外层冒泡) .prevent 阻止默认事件(链接跳转.表单提交) .capture 添加事件侦听器时使用事件捕获模式(从外到里触发事件) .self 只当事件在该元素本 ...
- 【JVM】符号引用和直接引用
在JVM中类加载过程中,在解析阶段,Java虚拟机会把类的二级制数据中的符号引用替换为直接引用. 1.符号引用(Symbolic References): 符号引用以一组符号来描述所引用的目标,符号可 ...