2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




Начать новую тему Ответить на тему
 
 Неопределенное поведение
Сообщение29.01.2014, 14:23 
Заслуженный участник
Аватара пользователя


28/07/09
1178
Всем привет, имеется структура типа "очередь" . И небольшая программа с использованием буфера как раз типа "очередь". Программа работает, однако компилятор gcc объявляет один warning, который мне совершенно не понятен.
В сухом остатке мне нужно показать вам следующий код. Вы можете его скомпилировать (на gcc с ключом -Wall) и получить то же предупреждение, что и я.
Используется синтаксис C
struct q
{
    char *s;
    int num;
};
void add(struct q *buf, char c)
{
    *((buf->s=realloc(buf->s, ++buf->num*sizeof(char)))+buf->num-1)=c;
}
 

Функция add добавляет в буфер один символ, изменяет размер буфера, увеличивает счётчик символов в буфере на единицу.
Да, я люблю однострочники и считаю их допустимыми в программах "для себя" :D Кому трудно прочитать выражение, дальше есть дерево.
А warning вот какой:
Используется синтаксис Bash
test.c: В функции «add»:
test.c:12:31: предупреждение: операция над «buf->num» может дать неопределенный результат [-Wsequence-point]


В warning'е речь идёт о точках следования, и для лучшего понимания я нарисовал дерево этого выражения, отметив на нём sequence points как SP (не считая самого внешнего на точке с запятой). Мог кстати и не правильно отметить, но дерево вроде бы верное.
Изображение

Так вот, почему тут возникает это предупреждение и какая именно есть неоднозначность в операции над buf->num? Я её в упор не вижу.

 Профиль  
                  
 
 Re: Неопределенное поведение
Сообщение29.01.2014, 14:49 


10/04/12
704
Вся проблема в инкременте. ++buf->num и потом buf->num-1. По стандарту очередность выполнения инкремента не определена. Поэтому значение выражения buf->num-1 не определено, может использоваться значение buf->num до инкремента, а может использоваться значение после инкремента.

 Профиль  
                  
 
 Re: Неопределенное поведение
Сообщение29.01.2014, 18:40 
Заслуженный участник


09/09/10
3729
Legioner93 в сообщении #820303 писал(а):
Да, я люблю однострочники и считаю их допустимыми в программах "для себя" :D

Хотите интересное упражнение? Соберите программу с "однострочниками", потом перепишите ее в человеческом виде, соберите снова и сравните два исполняемых файла. Объясните результат.

Ну и да, mustitz сказал все верно — у вас под правой sequence point один раз разыменовывается buff, а другой раз — ++buff.

Пожалуйста, не пишите запутанный код, если вы видите меньше UB, чем компилятор видит (а он их видеть абсолютно не обязан, кстати, в отличие от вас).

 Профиль  
                  
 
 Re: Неопределенное поведение
Сообщение29.01.2014, 20:01 
Заслуженный участник


04/05/09
4582
Joker_vD в сообщении #820382 писал(а):
Ну и да, mustitz сказал все верно — у вас под правой sequence point один раз разыменовывается buff, а другой раз — ++buff.
Не так. В другой раз также разыменовывается buf, а ++ относится к (buf->num).

-- Ср янв 29, 2014 12:03:41 --

Legioner93 в сообщении #820303 писал(а):
Да, я люблю однострочники
Кстати, если вы так любите короткий код, то зачем умножаете на sizeof(char)? Это ведь равно единице по определению оператора sizeof().

 Профиль  
                  
 
 Re: Неопределенное поведение
Сообщение30.01.2014, 01:46 


10/04/12
704
venco в сообщении #820426 писал(а):
Joker_vD в сообщении #820382 писал(а):
Ну и да, mustitz сказал все верно — у вас под правой sequence point один раз разыменовывается buff, а другой раз — ++buff.
Не так. В другой раз также разыменовывается buf, а ++ относится к (buf->num).


Правильно, ++ относится к buf->num. Что делает выражение buf->num неопределенным, если оно встретится еще где либо в этом же выражении. А оно встречается чуть далее по тексту. Поэтому компилятор может выбирать один из двух вариантов:

Используется синтаксис C
void add1(struct q *buf, char c)
{
    auto new_buf_num = buf_num + 1;
    *((buf->s=realloc(buf->s, ++buf->num*sizeof(char)))+new_buf_num-1)=c;
}

void add2(struct q *buf, char c)
{
    auto old_buf_num = buf->num;
    *((buf->s=realloc(buf->s, ++buf->num*sizeof(char)))+old_buf_num-1)=c;
}


Но даже если инкремент обернуть в функцию, то это не избавит от UB. На скане приведено красивое дерево. По стандарту языка C (C++), оператор сложения + не определяет порядок вычисления аргументов, поэтому в выражении f1()+f2() возможно, что любая из функций может быть вычислена первой. В нашем случае, если на скане для узла + вначале вычислить левое поддерево, то мы получим add1, а если вначале вычислить правое поддерево, то add2. Другой пример:

Используется синтаксис C
static inline increment(int * value)
{
    return ++*value;
}

int x = 0;
int a = increment(&x) + x; // UB
int b = increment(&x) && x; // 1
 


Ну а так в каждом языке свои правила, поэтому, чтобы не забиваться себе голову, я взял себе за правило не писать код, который содержит выражения, значения которых зависят от порядка вычисления аргументов функций/операторов. Имхо, так получается и более читабельно, и надо меньше ячеек в мозгу занято хранением деталей.

 Профиль  
                  
 
 Re: Неопределенное поведение
Сообщение30.01.2014, 09:46 
Заслуженный участник
Аватара пользователя


28/07/09
1178
mustitz в сообщении #820314 писал(а):
Вся проблема в инкременте. ++buf->num и потом buf->num-1. По стандарту очередность выполнения инкремента не определена.

Спасибо, вы совершенно правы! Меня немного запутали эти sequence points (на выходе и входе из realloc). Сейчас побольше почитал про них, и стало всё ясно. Действительно, никакие sequence points не помешают компилятору вычислить то дерево в угодном ему порядке.
mustitz в сообщении #820568 писал(а):
Ну а так в каждом языке свои правила, поэтому, чтобы не забиваться себе голову, я взял себе за правило не писать код, который содержит выражения, значения которых зависят от порядка вычисления аргументов функций/операторов. Имхо, так получается и более читабельно, и надо меньше ячеек в мозгу занято хранением деталей.

Я предполагаю (сам никогда не участвовал ещё), что в реальных проектах намного эффективнее по совокупному времени, потраченному на код, писать в более читабельном стиле. Потому что там код не только пишут, но ещё и читают, причем много-много раз.
Однако во время изучения языка (чем я сейчас и занимаюсь) полезно покопаться во всех этих тонкостях. Хотя бы чтобы лучше понять, почему так всё-таки не надо писать :D
Joker_vD в сообщении #820382 писал(а):
Хотите интересное упражнение? Соберите программу с "однострочниками", потом перепишите ее в человеческом виде, соберите снова и сравните два исполняемых файла. Объясните результат.

Предполагаю, что существенной разницы не будет. Но мне нравится писать такие выражения, потому что их писать быстрее! В смысле набора с клавиатуры. Да и какая-то своя эстетика у них есть :-)
И да, я знаю, что пока время, затраченное например на эту тему с лихвой перебило все эти 2-3 секундные выигрыши :mrgreen:
Joker_vD в сообщении #820382 писал(а):
Пожалуйста, не пишите запутанный код, если вы видите меньше UB, чем компилятор видит (а он их видеть абсолютно не обязан, кстати, в отличие от вас).

Ну, это только пока. Как говорится, догоним и перегоним компилятор. Я вот только недавно сел серьезно изучать C, поэтому и создаю подобные темы, дабы изучить язык на хорошем уровне.
Тут же как, я считаю: если программист "на ты" с этими sequence points, undefined behaviour, unspecified behavior и прочими хаками, то он прекрасно будет понимать, как эффективно и правильно написать и без них, когда это нужно.
venco в сообщении #820426 писал(а):
Кстати, если вы так любите короткий код, то зачем умножаете на sizeof(char)? Это ведь равно единице по определению оператора sizeof().

Согласен, спасибо! Теперь буду заменять на 1. Как-то в памяти просто засело, что на некоторых системах $char$ может быть и 2 октета, и 0.5, вот и писал по привычке. Но это никакого отношения не имеет к sizeof(char), который всегда 1 по стандарту языка, как только что выяснилось :-)

 Профиль  
                  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 6 ] 

Модераторы: Karan, Toucan, PAV, maxal, Супермодераторы



Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group