【问题标题】:Is modifying an object through a pointer to const undefined behavior?是否通过指向 const 未定义行为的指针来修改对象?
【发布时间】:2019-06-17 09:32:44
【问题描述】:

假设我们有一个引用为const int*int 变量,而该变量又别名为int *。如果通过int * 指针修改变量是否是未定义的行为,标准是否清楚?

作为说明,请考虑以下代码:

void increment(int* p) {
    (*p)++;
}

void call_increment(const int* p) {
    increment(p);
}

int main(void) {
    int x = 7;
    int* p = &x;

    call_increment(p);
}

【问题讨论】:

  • 您显示的代码格式错误,资格转换无效
  • GCC 的默认值:warning: passing argument 1 of ‘increment’ discards ‘const’ qualifier from pointer target type

标签: c pointers constants language-lawyer undefined-behavior


【解决方案1】:

通过指向const 的指针修改对象是不正确的,而不是未定义的行为。
除非引用的对象实际上是 const,否则通过丢弃 const 来解决这个问题。

您的代码有不同的问题:
在将 pcall_increment() 传递到 increment() 时,您正在丢弃 const 限定符。

任何有用的编译器都会抱怨even without being prompted

g++ -x c -std=c18 main.cpp && ./a.out
main.cpp: In function 'call_increment':
main.cpp:6:15: warning: passing argument 1 of 'increment' discards 'const' qualifier from pointer target type [-Wdiscarded-qualifiers]
     increment(p);
               ^
main.cpp:1:21: note: expected 'int *' but argument is of type 'const int *'
 void increment(int* p) {
                ~~~~~^

请注意,最好至少使用-Wall -Wextra 要求更多。

【讨论】:

  • 是的。但是任何转换都暗示了一个潜在的错误,因为它覆盖了(最小的)护栏,所以如果可行的话,应该通过重构来避免。
  • 您应该提到将 const int * 转换回 int * 是有效的,并且由于 C 2018 6.3.2.3 7 导致指向原始对象的指针:“指向对象的指针type 可以转换为指向不同对象类型的指针。如果结果指针未正确对齐引用的类型,则行为未定义。否则,当再次转换回来时,结果将等于原始指针。”
  • @EricPostpischil intint 相比,不是不同的对象类型。是同一类型,所以6.3.2.3/7不适用。
  • @Lundin: const intint 是不同的类型。
  • @Lundin:第 2 段说您可以添加限定符。第 7 段说你可以回到原来的状态。第 7 段不是多余的,因为它允许您通过返回到较早的类型来删除最初不存在的限定符(但不是最初作为对象定义一部分的限定符),第 2 段没有涉及,因为它只是关于添加限定符.
【解决方案2】:

C 中的const 限定符指定左值不会用于修改对象,但通常没有说明是否可以通过其他方式修改对象,包括通过从左值派生的非 const 限定指针有问题。

它确实对对象有影响的两个值得注意的情况:

    1234563 .如果顶级左值是const volatile,则标准不会预期该值可能通过任何特定方式更改,但质量实现通常应允许该值可能通过他们一无所知的方式自发更改的可能性。
  1. 如果指向const 对象的指针被限定为restrict,则通过该指针观察到的任何对象或从其中存储的地址派生的任何左值必须在指针的整个活动生命周期内具有相同的值。因此,给定例如

    int test(int const *restrict p)
    {
      if (*p == 1)
      {
        doSomething(p);
        return *p;
      }
      else
        return 1;
    }
    

    允许编译器生成返回 1 的代码,而无需重新加载 *p,但如果没有 restrict 限定符,则不允许这样做。这也适用于例如

    int test(int const *p)
    {
      int const * restrict pp = p;
      if (*pp == 1) // Restrict-qualified pointer used to observe value
      {
        doSomething(pp);
        return *pp;
      }
      else
        return 1;
    }
    

    但不是

    int test(int const *p)
    {
      int const * restrict pp = p;
      if (*p == 1)
      {
        doSomething(pp);
        return *p;
      }
      else
        return 1;
    }
    

    如果p 的副本先前已存储在全局对象中并且doSomething 将完全忽略pp,则对*p 的更改不会影响通过从pp 派生的指针访问的任何对象。

如果想要通过指示通过指针标识的对象永远不会改变来最大化优化,除了将对象标识为const之外,通常应该将指针限定为restrict

【讨论】:

    猜你喜欢
    • 2014-05-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-09
    • 1970-01-01
    • 1970-01-01
    • 2021-06-04
    • 1970-01-01
    相关资源
    最近更新 更多