【问题标题】:In C, can a const variable be modified via a pointer?在 C 中,可以通过指针修改 const 变量吗?
【发布时间】:2013-03-22 17:01:55
【问题描述】:

我在我的代码中写了一些类似的东西

const int x=1;
int *ptr;
ptr = &x;
*ptr = 2;

这适用于所有编译器吗?为什么 GCC 编译器没有注意到我们正在更改常量变量?

【问题讨论】:

  • 确实如此。您应该会收到类似warning: assignment discards qualifiers from pointer target type 的消息,指的是ptr = &x; 这一行。
  • 这也有一些信息:stackoverflow.com/questions/945640/…
  • 假设,编译器可以将const int 放入只读内存。然后*ptr = 2; 会崩溃。事实上,不要指望它会崩溃。哦,如果你允许的话,编译器会抱怨丢弃 ptr = &x; 上的限定符。
  • 我的编译器声明错误ptr = &x;不兼容的类型'int *'和'const int *
  • @BillHicks 我曾经被刀刺伤但没有死……这是否证明刺伤并不致命?

标签: c pointers constants


【解决方案1】:

const 实际上并不意味着“恒定”。 C 语言中的“常量”具有在编译时确定的值;文字 42 就是一个例子。 const 关键字实际上意味着只读。例如:

const int r = rand();

r 的值直到程序执行时才确定,但const 关键字意味着您在初始化后不允许修改r。

在您的代码中:

const int x=1;
int *ptr;
ptr = &x;
*ptr = 2;

赋值ptr = &x; 是一个违反约束,这意味着需要一个符合要求的编译器来抱怨它;您不能合法地将 const int*(指向 const int 的指针)值分配给非 const int* 对象。 如果编译器生成一个可执行文件(它不需要这样做;它可以直接拒绝它),那么该行为不是由 C 标准定义的。

例如,生成的代码实际上可能将值 2 存储在 x 中——但随后对 x 的引用可能会产生值 1,因为编译器知道 x 在初始化后不能被修改。它知道,因为你告诉它,通过将x 定义为const。如果你对编译器撒谎,后果可能是任意的。

实际上,可能发生的最糟糕的事情是程序的行为与您期望的一样;这意味着您有一个很难检测到的错误。 (但你应该得到的诊断结果将是一个很大的线索。)

【讨论】:

  • 标准参考:C11 6.5.4/3
  • This compiler 允许这样的声明:int *ptr = (int*)&x; 但不允许int *ptr = &x;。此外,printf("%d %d",x,*ptr); 输出结果1 2,而printf("%p %p",&x, ptr); 为&x 和ptr 输出相同的地址(类似于0x7ffe6a1aa47c)。这算作错误/错误还是有目的?这种行为是语言相关还是编译器相关?
  • @Xfce4 我认为int *ptr = (int*)&x; 是程序员的错误。我希望大多数 C 编译器都能毫无怨言地接受它(不需要诊断)。强制转换意味着转换不是违反约束的,但它确实具有未定义的行为。给定const int x = 1;,编译器可以合理地假设x 继续具有1 的值,并且不需要生成代码来再次读取它。如果您背着编译器将2 写入该内存位置,那么您对编译器撒了谎,它会报复。
  • @Xfce4 值和地址直接存储。不直截了当的是,编译器知道x 是用const 定义的,所以它假定它的值不能改变,所以它将对x 的引用替换为相当于文字1 的引用。这是一个完全有效的优化。这里给出了意想不到的结果,因为程序员作弊了。
  • @Xfce4 我想我已经回答了。实现的义务是产生程序的定义行为。它生成的任何代码都只是达到此目的的一种手段。 (如果程序有未定义的行为,所有的赌注都没有。)
【解决方案2】:

Online C 2011 draft:

6.7.3 类型限定符

...
6 如果尝试通过使用来修改使用 const 限定类型定义的对象 具有非 const 限定类型的左值,行为未定义。如果尝试是 通过使用左值来引用使用 volatile 限定类型定义的对象 对于非易失性限定类型,行为未定义。133)
133) 这适用于那些表现得好像它们是用限定类型定义的对象,即使它们是 从未真正定义为程序中的对象(例如内存映射输入/输出处的对象 地址)。

添加了重点。

由于行为未定义,编译器不需要发出诊断,也不需要停止翻译。这在一般情况下很难捕捉到;假设你有一个类似的功能

void foo( int *p ) { *p = ...; }

在它自己独立的翻译单元中定义。在翻译过程中,编译器无法知道p 是否可以指向const-qualified 对象。如果您的电话类似于

const int x;
foo( &x );

您可能收到类似parameter 1 of 'foo' discards qualifiers 或类似提示的警告。

还要注意const 限定符并不一定意味着相关变量将存储在只读内存中,因此上述代码可能会“工作”(更新x 中的值)通过围绕const 语义进行结束运行,您将成功更新x。但是你也可以不将x 声明为const。

【讨论】:

  • 在我们达到 6.7.3/6 之前,还需要提一下 OP 的代码违反了约束。 (参考C11 6.5.4/3)
【解决方案3】:

糟糕的程序员。没有月饼!

如果您需要修改 const,请将其复制到非 const 变量中,然后使用它。这是 const 是有原因的。试图“偷偷摸摸”绕过 const 可能会导致严重的运行时问题。即优化器可能使用了内联值等。

const int x=1;
int non_const_x = x;
non_const_x = 2;

【讨论】:

  • 如果需要修改const(非常量)对象,首先不要定义为const。
【解决方案4】:

这里有一个很好的讨论:Does the evil cast get trumped by the evil compiler?

我希望 gcc 能够编译它,因为:

  • ptr 可以指向 x,否则将无法读取它,尽管正如下面的评论所说,它并不是完全出色的代码,编译器应该会抱怨。警告选项(我猜)会影响它是否实际被警告。
  • 当您写入 x 时,编译器不再“知道”它正在写入 const,这一切都在 C 中的编码人员手中。但是,它确实知道,因此它可能会警告您,具体取决于您选择的警告选项。

但是,它是否有效取决于代码的编译方式、针对所选编译选项实现 const 的方式以及目标 CPU 和架构。它可能会起作用。或者它可能会崩溃。或者您可能会写入“随机”内存并导致(或不)一些怪异的效果。这不是一个好的编码策略,但这不是你的问题:-)

【讨论】:

  • ptr 不允许指向x;编译器应该(并且确实)抱怨。您需要const int *ptr 才能使分配有效。
猜你喜欢
  • 2011-01-31
  • 1970-01-01
  • 1970-01-01
  • 2017-04-27
  • 2021-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多