С какими проблемами вы столкнулись из-за точек последовательности в C и C ++? - PullRequest
4 голосов
/ 13 мая 2009

Ниже приведены две распространенные проблемы, приводящие к неопределенному поведению из-за правил точки последовательности:

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

С какими другими вещами вы столкнулись в отношении точек последовательности?

Действительно трудно обнаружить эти проблемы, когда компилятор не может предупредить нас.

Ответы [ 6 ]

10 голосов
/ 13 мая 2009

Вариант примера Дарио таков:

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 никогда не будет удален.

6 голосов
/ 13 мая 2009

Есть несколько неоднозначных случаев относительно порядка выполнения в списках параметров или, например, дополнения.

#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 () выполняются первыми? ; -)

3 голосов
/ 14 мая 2009

Вот простое правило из принципов и практик программирования с использованием c ++ Бьярна Страуступа

", если вы измените значение переменной в выражении. Не читать или напишите дважды в одном и том же выражении "

a[i] = i++; //i's value is changed once but read twice
i = i++;   //i's value is changed once but written twice
1 голос
/ 14 мая 2009

То, что я видел недавно, было связано с желанием программиста сэкономить время на форматирование классов, совершенно неуместное:


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 :) */

1 голос
/ 14 мая 2009

Вот два хороших выражения, которые работают для большинства компиляторов Си, но они неоднозначны из-за точек последовательности:

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!
1 голос
/ 13 мая 2009

Пример, подобный Дарио, в который я также видел людей, попадающих в:

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

Мало того, что это напечатает «addr1 addr1» или «addr2 addr2» (потому что inet_ntoa возвращает указатель на статический буфер, перезаписанный последующими вызовами), но также не определено, какой из них будет case (потому что C не определяет порядок вычисления в списках аргументов).

...