Каково общее неопределенное/неуказанное поведение для C, с которым Вы сталкиваетесь? [закрытый]

Примером неуказанного поведения на языке C является порядок оценки аргументов функции. Это могло бы быть слева направо или справа налево, Вы просто не знаете. Это влияло бы как foo(c++, c) или foo(++c, c) оценен.

Что другое неуказанное поведение там, который может удивить не зная программиста?

63
задан JL2210 24 August 2019 в 15:48
поделиться

10 ответов

Адвокат языка вопрос. Hmkay.

Мой персональный top3:

  1. нарушение строгого правила
  2. искажения, нарушающего строгое правило
  3. искажения, нарушающее строгое правило

    искажения:-)

, Редактирование Вот является небольшим примером, который делает это неправильно дважды:

(принимают 32 бита ints и прямой порядок байтов)

float funky_float_abs (float a)
{
  unsigned int temp = *(unsigned int *)&a;
  temp &= 0x7fffffff;
  return *(float *)&temp;
}

, Что код пытается получить абсолютное значение плавания битовым жонглированием со знаковым битом непосредственно в представлении плавания.

Однако результатом создания указателя на объект путем кастинга от одного типа до другого не является допустимый C. Компилятор может предположить, что указатели на различные типы не указывают на тот же блок памяти. Это верно для всего вида указателей кроме пустоты*, и символ* (мыс знака не имеет значения).

В случае выше я делаю это дважды. Однажды для получения международного псевдонима для плавания a, и однажды преобразовать значение назад для плавания.

существует три допустимых способа сделать то же.

Использование символ или пустой указатель во время броска. Они всегда искажают к чему-либо, таким образом, они безопасны.

float funky_float_abs (float a)
{
  float temp_float = a;
  // valid, because it's a char pointer. These are special.
  unsigned char * temp = (unsigned char *)&temp_float;
  temp[3] &= 0x7f;
  return temp_float;
}

Использование memcopy. Memcpy берет пустые указатели, таким образом, он вызовет искажение также.

float funky_float_abs (float a)
{
  int i;
  float result;
  memcpy (&i, &a, sizeof (int));
  i &= 0x7fffffff;
  memcpy (&result, &i, sizeof (int));
  return result;
}

третий допустимый путь: используйте объединения. Это явно не не определено начиная с C99:

float funky_float_abs (float a)
{
  union 
  {
     unsigned int i;
     float f;
  } cast_helper;

  cast_helper.f = a;
  cast_helper.i &= 0x7fffffff;
  return cast_helper.f;
}
72
ответ дан user3447428 24 November 2019 в 16:08
поделиться

Используя макро-версии функций как "макс." или "isupper". Макросы оценивают их аргументы дважды, таким образом, Вы получите неожиданные побочные эффекты, когда Вы будете звонить макс. (++ я, j), или isupper (*p ++)

, Вышеупомянутое для стандарта C. В C++ в основном исчезли эти проблемы. Макс. функция является теперь шаблонной функцией.

0
ответ дан Mike Thompson 4 July 2019 в 19:46
поделиться

упущение добавить static float foo(); в заголовочном файле, только получить исключения в операции с плавающей запятой, бросаемые, когда это возвратилось бы 0.0f;

-1
ответ дан Nicholas Mancuso 4 July 2019 в 19:46
поделиться

Мое любимое неопределенное поведение состоит в том, что, если непустой исходный файл не заканчивается в новой строке, поведение не определено.

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

Так для подлинных проблем мобильности (которые главным образом являются зависящими от реализации, а не неуказанными или неопределенными, но я думаю, что это попадает в дух вопроса):

  • символ не обязательно подписанная (ООН).
  • интервал может быть любым размером от 16 битов.
  • плавания не обязательно отформатированы IEEE или совместимы.
  • целые типы являются не обязательно дополнением two, и целочисленное арифметическое переполнение вызывает неопределенное поведение (современные аппаратные средства не откажут, но некоторая оптимизация компилятора приведет к поведению, отличающемуся от переноса даже при том, что это - то, что делают аппаратные средства. Например if (x+1 < x) может быть оптимизирован как всегда ложь, когда x подписал тип: см. -fstrict-overflow опция в GCC).
  • " / ", "." и ".." в #include не имеют никакого определенного значения и может рассматриваться по-другому различными компиляторами (это действительно на самом деле варьируется, и если он пойдет не так, как надо, то он разрушит Ваш день).

Действительно серьезные, которые могут быть удивительными даже на платформе, на которой Вы разработали, потому что поведение только частично не определено / неуказанный:

  • поточная обработка POSIX и модель памяти ANSI. Параллельный доступ к памяти также не определяется, как новички думают. энергозависимый не делает, какие новички думают. Порядок доступов памяти также не определяется, как новички думают. Доступы могут быть перемещенными через барьеры памяти в определенных направлениях. Когерентность кэша памяти не требуется.

  • Профильный код не так легок, как Вы думаете. Если Ваш испытательный шлейф не имеет никакого эффекта, компилятор может удалить часть или все это. встроенный не имеет никакого определенного эффекта.

И, как я думаю, Nils упомянул мимоходом:

  • НАРУШЕНИЕ СТРОГОГО ПРАВИЛА ИСКАЖЕНИЯ.
30
ответ дан Steve Jessop 24 November 2019 в 16:08
поделиться

Деление чего-то указателем на что-то. Просто не скомпилирует по некоторым причинам...:-)

result = x/*y;
22
ответ дан HostileFork 24 November 2019 в 16:08
поделиться

Мой фаворит - это:

// what does this do?
x = x++;

Для ответа на некоторые комментарии это - неопределенное поведение согласно стандарту. Видя это, компилятору позволяют сделать что-либо до и включая формат Ваш жесткий диск. Посмотрите, например этот комментарий здесь . Точка не то, что Вы видите, что существует возможное разумное ожидание некоторого поведения. Из-за стандарта C++ и пути определяются точки последовательности, эта строка кода является на самом деле неопределенным поведением.

, Например, если бы мы имели x = 1 перед строкой выше, тогда чем допустимое закончилось бы быть впоследствии? Кто-то прокомментировал, что это должно быть

, x увеличен 1

, таким образом, мы должны видеть x == 2 впоследствии. Однако это не на самом деле верно, Вы найдете некоторые компиляторы, которые имеют x == 1 впоследствии, или возможно даже x == 3. Необходимо было бы пристально смотреть на сгенерированный блок для наблюдения, почему это могло бы быть, но различия происходят из-за базовой проблемы. По существу я думаю, что это вызвано тем, что компилятору позволяют оценить эти два оператора присвоений в любом порядке, который он любит, таким образом, он мог сделать x++ первый, или x = сначала.

19
ответ дан 1800 INFORMATION 24 November 2019 в 16:08
поделиться

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

символ является злым.

  • подписанный или неподписанный в зависимости от того, что компилятор чувствует себя
  • не переданным под мандат как 8 битов
10
ответ дан itj 24 November 2019 в 16:08
поделиться

Компилятор не должен говорить Вам, что Вы вызываете функцию с неправильным количеством типов параметра параметров/несправедливости, если прототип функции не доступен.

7
ответ дан mbac32768 24 November 2019 в 16:08
поделиться

EE, здесь просто обнаруженный, что a>>-2 является немного удручающим.

я кивнул и сказал им, что это не было естественно.

2
ответ дан Tim Williscroft 24 November 2019 в 16:08
поделиться

Обязательно всегда инициализируйте Ваши переменные перед использованием их! Когда я только что запустил с C, который вызвал меня много головных болей.

1
ответ дан William Keller 24 November 2019 в 16:08
поделиться
Другие вопросы по тегам:

Похожие вопросы: