5

考虑以下 2 个程序prog1prog2 。如果我尝试更改consti使用指针更改限定变量的值ptr,我会收到警告(不是错误)"initialization discards qualifiers from pointer target type|",但程序仍然运行并显示新值。但是如果我尝试i使用赋值语句更改第二个程序中的值,我得到错误(不是警告)assignment of read-only variable 'i'|

以下是由这个前提引起的混淆:

1)为什么我们可以在任何情况下更改只读const限定变量的值?这不是违背了使用const限定符的目的吗?如果我们尝试这样做,难道不应该得到错误吗?

2)即使由于某些奇怪的原因我们被允许更改常量的值,为什么要区分更改只读的值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
}
4

2 回答 2

9

1) 为什么我们可以在任何情况下更改只读const限定变量的值?它不会破坏使用const限定符的目的吗?

尝试通过赋值运算符更改 const 限定的对象是违反约束的:

6.5.16 在约束下:

2 赋值运算符应有一个可修改的左值作为其左操作数。

可修改的左值在 6.3.2.1 (1) 中定义:

修改的左值是没有数组类型,没有不完整类型,没有 const 限定类型,并且如果它是结构或联合,则没有任何成员(包括递归地,任何成员或所有包含的聚合或联合的元素)具有 const 限定类型。

作为约束违规,它需要编译器根据 5.1.1.3 (1) 提供诊断消息:

如果预处理翻译单元或翻译单元包含违反任何语法规则或约束的行为,则符合要求的实现应产生至少一个诊断消息(以实现定义的方式标识),即使该行为也明确指定为未定义或实现-定义。在其他情况下不需要生成诊断消息。

但是不需要实现来拒绝无效程序,因此诊断消息也可以是警告而不是错误。

const但是,修改通过没有 const 限定类型的左值声明的对象不是违反约束,尽管它会调用未定义的行为,6.7.3 (6):

如果尝试通过使用具有非 const 限定类型的左值来修改使用 const 限定类型定义的对象,则行为未定义。

由于它不是违反约束也不是无效语法,因此它甚至不需要发出诊断消息。

如果我们尝试这样做,我们不应该得到一个错误吗?

如果您尝试通过具有 const 限定类型的左值修改对象,则必须获得诊断消息。

由于这严重违反了声明的意图,因此大多数编译器在这些情况下都会发出错误。

如果您尝试通过具有非 const 限定类型的左值来修改具有 const 限定类型的对象,如

const int i=8;
int *ptr=&i;
*ptr=9;

i通过表达式进行修改的尝试会*ptr = 9调用未定义的行为,但不是违反约束(或语法错误),因此不需要诊断消息(并且没有给出)。

为初始化发出了诊断消息

int *ptr = &i;

因为这再次违反了约束,根据 6.5.16.1 (1):

应满足下列条件之一:

  • 左操作数具有原子、合格或非限定算术类型,右操作数具有算术类型;
  • 左操作数具有与右操作数兼容的结构或联合类型的原子、限定或非限定版本;
  • 左操作数具有原子、限定或非限定指针类型,并且(考虑左操作数在左值转换后将具有的类型)两个操作数都是指向兼容类型的限定或非限定版本的指针,并且左侧指向的类型具有所有right 指向的类型的限定符
  • 左操作数具有原子、限定或非限定指针类型,并且(考虑左操作数在左值转换后将具有的类型)一个操作数是指向对象类型的指针,另一个是指向限定或非限定版本的指针void,并且left指向的类型具有right指向的类型的所有限定符;
  • 左操作数是原子的、合格的或不合格的指针,右操作数是空指针常量;或者
  • 左操作数的类型为 atomic、qualified 或 unqualified _Bool,而右操作数是指针。

但是,该诊断通常是警告而不是错误,因为人们可能会明确地抛弃它const

int *ptr = (int*)&i;

而不能抛弃constfrom i

如果指向的对象是 modifiable ,则通过指向非 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 限定类型的指针转​​换为指向相应非 const 限定类型的指针,并且通过该指针修改指针可能是有效的,因此您只会收到警告,并且只有在未进行转换的情况下明确的。

在给定的简短示例中,编译器可以检测到指针对象具有 const-qualified 类型,因此修改会调用未定义的行为,但通常这很难,而且通常无法检测到。因此,编译器甚至不会尝试检测简单的情况,这是不值得的。

于 2013-05-14T13:38:40.767 回答
2

为什么我们可以在任何情况下更改只读const限定变量的值?

我们不是。我不明白你为什么这么认为,也不知道哪个例子表明了这一点。

为什么区分使用指针更改只读 const 限定变量的值(这是允许的,但有警告)

再说一遍:这是不允许的,因此发出警告。(警告要被认真对待——你似乎没有给他们任何意义......)只是编译器不知道指针指向某个const-qualified 对象(因为它被声明为 non-const T *) .

至于为什么更改变量有效:

解释#1:这是未定义的行为(违反约束),所以它可以做任何事情。

解释#2:可能它只是像本地自动变量一样存储在堆栈中,您确实可以更改它。

于 2013-05-14T12:15:38.607 回答