提问人:Rüppell's Vulture 提问时间:5/14/2013 最后编辑:Petter HesselbergRüppell's Vulture 更新时间:10/26/2023 访问量:3695
为什么我们被允许更改“const”限定变量的值?为什么允许指针,但不允许赋值?
Why are we allowed to change values of "const" qualified variables?Why pointers are allowed for this,but not assignment?
问:
请考虑以下 2 个程序 prog1 和 prog2。在这里,如果我尝试使用指针更改限定变量的值,我会收到警告(不是错误),但程序仍然运行并显示新值。但是,如果我尝试使用赋值语句更改第二个程序中的值,则会出现错误(不是警告)。const
i
ptr
"initialization discards qualifiers from pointer target type|"
i
assignment of read-only variable 'i'|
以下是由此前提引起的混淆:
为什么我们允许在任何情况下更改只读限定变量的值?这难道不违背使用限定符的目的吗?如果我们尝试这样做,我们不应该得到一个错误吗?
const
const
即使由于某种奇怪的原因,我们被允许更改常量的值,为什么在使用指针(允许,带有警告)和使用赋值操作(根本不允许并给我们错误)更改只读限定变量的值之间进行区分?
const
// prog1
#include <stdio.h>
int main ()
{
const int i=8;
int *ptr=&i;
*ptr=9;
printf("%d",*ptr); //Prints new value nevertheless
}
警告:初始化从指针目标类型中丢弃限定符|
//prog2
#include <stdio.h>
int main()
{
const int i=8;
i=10;
printf("%d",i);
}
错误:只读变量 'i' 的赋值|
编辑 H2CO3
在这里,我多次更改限定变量的值。我只收到一个警告,与const
prog1
//prog3
#include <stdio.h>
int main ()
{
const int i=8;
int *ptr=&i;
*ptr=9;
*ptr=10;
printf("%d",*ptr); // Prints 10
}
答:
为什么我们允许在任何情况下更改只读限定变量的值?
const
我们不是。我不明白你为什么这么认为,也不明白哪个例子表明了这一点。
为什么使用指针更改只读常量限定变量的值(这是允许的,带有警告)之间的区分
再说一遍:这是不允许的,因此发出警告。(警告要认真对待——你似乎没有赋予它们任何意义......只是编译器不知道指针指向某个限定对象(因为它被声明为非常量)。const
T *
至于为什么更改变量有效:
解释 #1:这是未定义的行为(违反约束),因此它可以做任何事情。
解释#2:可能它只是像局部自动变量一样存储在堆栈上,你确实可以更改它。
评论
const
int *p = &foo;
p
1) 为什么我们允许在任何情况下更改只读限定变量的值?这难道不违背使用限定符的目的吗?
const
const
尝试通过赋值运算符更改 const 限定的对象是违反约束的:
6.5.16 约束下:
2 赋值运算符应有一个可修改的左值作为其左操作数。
6.3.2.1 (1) 中定义了可修改的左值:
可修改的左值是没有数组类型、不完全类型、没有常量限定类型的左值,如果它是结构或联合,则没有任何成员(包括递归地包括所有包含的聚合或联合的任何成员或元素)具有常量限定类型。
作为约束冲突,它需要根据 5.1.1.3 (1) 从编译器发出诊断消息:
如果预处理翻译单元或翻译单元包含违反任何语法规则或约束的内容,则符合要求的实现应至少生成一条诊断消息(以实现定义的方式标识),即使该行为也被显式指定为未定义或实现定义。在其他情况下不需要生成诊断消息。
但是,不需要实现来拒绝无效程序,因此诊断消息也可能是警告而不是错误。
但是,修改通过非 const 限定类型的左值声明的对象不是违反约束,尽管它调用了未定义的行为,6.7.3 (6):const
如果尝试通过使用具有非 const 限定类型的左值来修改用 const 限定类型定义的对象,则该行为是未定义的。
由于它不是约束冲突或无效语法,因此它甚至不需要发出诊断消息。
如果我们尝试这样做,我们不应该得到一个错误吗?
如果尝试通过具有 const 限定类型的左值修改对象,则必须收到诊断消息。
由于这严重违反了声明的意图,因此大多数编译器在这些情况下都会发出错误。
如果尝试通过具有非 const-qualifed 类型的左值来修改具有 const 限定类型的对象,如
const int i=8;
int *ptr=&i;
*ptr=9;
尝试通过表达式进行修改会调用未定义的行为,但不是约束冲突(或语法错误),因此不需要诊断消息(并且没有给出任何消息)。i
*ptr = 9
初始化发出诊断消息
int *ptr = &i;
因为这又是违反约束的,所以根据 6.5.16.1 (1):
以下其中一项应成立:
- 左操作数有原子、限定或非限定算术类型,右操作数有算术类型;
- 左操作数具有与右操作数类型兼容的结构或联合类型的原子、限定或非限定版本;
- 左操作数具有原子指针类型、限定指针或非限定指针类型,并且(考虑左操作数在左值转换后将具有的类型)两个操作数都是指向兼容类型的限定或非限定版本的指针,并且左指向的类型具有右指向的类型的所有限定符;
- 左操作数具有原子、限定或非限定指针类型,并且(考虑到左操作数在左值转换后将具有的类型)一个操作数是指向对象类型的指针,另一个是指向 void 的限定或非限定版本的指针,并且左指向的类型具有右指向的类型的所有限定符;
- 左边的操作数是原子的、限定的或非限定的指针,右边是空指针常量;或
- 左操作数具有类型 atomic、qualified 或 unqualified _Bool,右操作数是指针。
然而,这种诊断通常是一个警告,而不是一个错误,因为人们可能会明确地抛弃,const
int *ptr = (int*)&i;
而一个人不能抛弃 .const
i
如果指向的对象是可修改的,则通过指向非 const 限定对象类型的指针修改对象类型(该对象类型是通过将指向 const 限定对象类型的指针强制转换而获得的)是有效的。愚蠢的例子:const
int i = 8;
const int *cptr = &i; // valid, no problem adding const
int *mptr = (int*)cptr;
*mptr = 9; // no problem, pointee is non-const
2)即使由于某种奇怪的原因,我们被允许更改常量的值,为什么在使用指针(允许,带有警告)和使用赋值操作(根本不允许并给我们错误)更改只读常量限定变量的值之间进行区分?
直接赋值给具有 const 限定类型的对象不仅是违反约束,而且明显违反了声明的语义。声明一个对象明确表示“我不希望修改该对象”。const
通过指向非 const 限定类型的指针修改对象不是违反约束,只有当指针具有 const 限定类型时,才是未定义的行为。允许将指向 const 限定类型的指针转换为指向相应非常量限定类型的指针,并且通过该指针修改指针可能是有效的,因此您只会收到警告,并且仅当转换未显式时才会收到警告。
在给定的简短示例中,编译器可以检测到指针具有 const 限定类型,因此修改会调用未定义的行为,但通常这很困难,并且通常无法检测到。因此,编译器甚至不会尝试检测简单的情况,这不值得付出努力。
评论
int const* ptr = &i