【问题标题】:Which issues have you encountered due to sequence points in C and C++?由于 C 和 C++ 中的序列点,您遇到过哪些问题?
【发布时间】:2010-10-25 22:58:36
【问题描述】:

以下是由于序列点规则导致未定义行为的两个常见问题:

a[i] = i++; //has a read and write between sequence points
i = i++;   //2 writes between sequence points

关于序列点,您还遇到过哪些其他事情?

当编译器无法警告我们时,很难找出这些问题。

【问题讨论】:

  • 没有问题。我只是想让标题更清楚一点,因为这听起来像是您在寻求特定问题的帮助。 :)

标签: c++ c sequence-points


【解决方案1】:

在参数列表或例如参数列表中存在一些关于执行顺序的模棱两可的情况。补充。

#include <iostream>

using namespace std;

int a() {
    cout << "Eval a" << endl;
    return 1;
}

int b() { 
    cout << "Eval b" << endl;
    return 2;
}

int plus(int x, int y) {
    return x + y;
}

int main() {

    int x = a() + b();
    int res = plus(a(), b());

    return 0;
}

a() 还是 b() 先执行? ;-)

【讨论】:

  • 关于直接添加,s/ambiguous/undefined/,按照标准(至少对于C)
  • 它对于 C++ 也是未定义的。这和模棱两可不太一样:)
【解决方案2】:

Dario 示例的一个变体如下:

void Foo(shared_ptr<Bar> a, shared_ptr<Bar> b){ ... }

int main() {
  Foo(shared_ptr<Bar>(new Bar), shared_ptr<Bar>(new Bar));
}

这可能会泄漏内存。两个参数的求值之间没有顺序点,因此不仅第二个参数可以在第一个参数之前求值,而且两个 Bar 对象也可以在 shared_ptr 的任何一个之前创建

也就是说,而不是被评估为

Bar* b0 = new Bar();
arg0 = shared_ptr<Bar>(b0);
Bar* b1 = new Bar();
arg1 = shared_ptr<Bar>(b1);
Foo(arg0, arg1);

(这是安全的,因为如果b0 被成功分配,它会立即被包裹在shared_ptr 中),它可以被评估为:

Bar* b0 = new Bar();
Bar* b1 = new Bar();
arg0 = shared_ptr<Bar>(b0);
arg1 = shared_ptr<Bar>(b1);
Foo(arg0, arg1);

这意味着如果b0被分配成功,而b1抛出异常,那么b0将永远不会被删除。

【讨论】:

    【解决方案3】:

    一个类似于达里奥的例子,我也看到人们陷入这种情况:

    printf("%s %s\n", inet_ntoa(&addr1), inet_ntoa(&addr2));
    

    这不仅会打印“addr1 addr1”或“addr2 addr2”(因为inet_ntoa返回一个指向被进一步调用覆盖的静态缓冲区的指针),而且还没有定义哪些将是case(因为 C 没有在参数列表中指定求值顺序)。

    【讨论】:

    • 这是关于 inet_ntoa 函数的错误规范。或者关于客户端代码的作者没有阅读它:) 这与序列点无关。
    • 嗯,是的,它两次打印相同地址的问题表明无法读取man inet_ntoa - 或者至少无法仔细考虑。但是,您不知道 which 地址将被打印两次的问题是由于缺少序列点。 (在 Java 中,类似的代码总是会打印第二个地址,因为它确实指定参数按照它们出现的顺序进行完全评估。)
    【解决方案4】:

    这是 Bjarne Stroustup 使用 c++ 的编程原则和实践中的一个简单规则

    "如果您更改表达式中变量的值。不要读取或 在同一个表达式中写两次”

    a[i] = i++; //i's value is changed once but read twice
    i = i++;   //i's value is changed once but written twice
    

    【讨论】:

      【解决方案5】:

      这里有两个很好的表达式,适用于大多数 C 编译器,但由于序列点而模棱两可:

      x ^= y ^= x ^= y; // in-place swap of two variables
      

      还有

      int i=0;
      printf("%d %d %d", ++i, ++i, ++i);  // usually prints out 3 2 1... but not for all compilers!
      

      【讨论】:

      • 你如何确定一个表达式是否未定义?你遵循的任何特殊规则
      • 就地交换只是愚蠢的。在 C++ 中使用 std::swap 或在 C 中使用临时变量。它比聪明的 XOR 技巧更快。
      【解决方案6】:

      我最近看到的一个是由于程序员希望节省类格式化时间,完全错了:

      class A { public: ... const char* Format( const string& f ) const { fmt = Print( f, value ); return fmt.c_str(); } operator const char* () const { return fmt.c_str(); } private: struct timeval value; mutable string fmt; }; A a( ... ); printf( "%s %s\n", a.Format( x ), a.Format( y );

      最后一行要么总是为两种格式打印相同的值(要么导致程序崩溃,因为内部字符串会释放返回的内存)。

      另一个是我很久以前的一次采访:

      
      void func( int x, int y, int z )
      {
          printf( "%d %d %d\n", x, y, z );
      }
      
      ...
      int i = 0;
      func( i, ++i, i++ ); /* don't do this in real software :) */
      
      

      【讨论】:

      • 是的,这与我的示例非常相似,但更糟糕的是因为可崩溃性。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-26
      • 2023-03-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-22
      相关资源
      最近更新 更多