связи c двойной битовое представление


TLDR; вызывает ли следующий код неопределенное (или неопределенное) поведение ?

#include <stdio.h>
#include <string.h>

void printme(void *c, size_t n)
{
  /* print n bytes in binary */
}

int main() {
  long double value1 = 0;
  long double value2 = 0;

  memset( (void*) &value1, 0x00, sizeof(long double));
  memset( (void*) &value2, 0x00, sizeof(long double));

  /* printf("value1: "); */
  /* printme(&value1, sizeof(long double)); */
  /* printf("value2: "); */
  /* printme(&value2, sizeof(long double)); */

  value1 = 0.0;
  value2 = 1.0;

  printf("value1: %Lfn", value1);
  printme(&value1, sizeof(long double));
  printf("value2: %Lfn", value2);
  printme(&value2, sizeof(long double));

  return 0;
}

На моей машине x86-64 выходные данные зависят от конкретных флагов оптимизации, передаваемых компилятору (gcc-4.8.0,- O0 vs-O1).

С -O0, я получаю

value1: 0.000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 
value2: 1.000000
00000000 00000000 00000000 00000000 00000000 00000000 00111111 11111111
10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

В то время как с -O1, я получаю

value1: 0.000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 
value2: 1.000000
00000000 00000000 00000000 00000000 00000000 01000000 00111111 11111111
10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 

Пожалуйста, обратите внимание на дополнительный 1 во второй последней строке. Кроме того, раскомментировав инструкции печати после memset, этот 1 исчезнет. Это, кажется, зависит от двух факты:

  1. long double дополняется, то есть sizeof(long double) = 16, но используется только 10 байт.
  2. вызов memset может быть оптимизирован
  3. биты заполнения длинных двойников могут меняться без предупреждения, т. е. операции с плавающей запятой на значение1 и значение2, похоже, скремблируют биты заполнения.

Я компилирую с -std=c99 -Wall -Wextra -Wpedantic и не получаю никаких предупреждений, поэтому я не уверен, что это случай нарушения строгого псевдонимирования (но это вполне может быть). Передача -fno-strict-aliasing не делает изменить что-то.

Контекст-это ошибка, найденная в библиотеке HDF5, описанной здесь. HDF5 делает некоторую битную манипуляцию, чтобы выяснить собственное битовое представление типов с плавающей запятой, но он запутывается, если биты заполнения не остаются нулевыми.

Итак:

  1. является ли это неопределенным поведением?
  2. является ли это нарушением строгого сглаживания?

Спасибо.

Edit: это код для printme. Я признаю, что только что вырезал и наклеил откуда-то снаружи слишком много внимания уделяет этому. Если вина здесь, я обойду вокруг стола со спущенными штанами.

void printme(void *c, size_t n)
{
  unsigned char *t = c;
  if (c == NULL)
    return;
  while (n > 0) {
    int q;
    --n;
    for(q = 0x80; q; q >>= 1) 
      printf("%x", !!(t[n] & q));
    printf(" ");
  }
  printf("n");
}
3 3

3 ответа:

Является ли это неопределенным поведением?

Да. Биты заполнения неопределенны (*). Доступ к неопределенной памяти может также быть неопределенным поведением (это было неопределенное поведение в C90, и некоторые компиляторы C99 трактуют его как неопределенное поведение. Кроме того, в обосновании C99 говорится, что доступ к неопределенной памяти должен быть неопределенным поведением. Но сам стандарт C99 не говорит об этом так ясно, он только намекает на ловушки представлений и может произвести впечатление что если человек знает, что у него нет представлений ловушек, он может получить неопределенные значения из неопределенной памяти). Заполняющая Часть long double, по крайней мере, не определена.

В сноске 271 К99 говорится: "содержание " отверстий", используемых в качестве подкладки для выравнивания внутри объектов структуры, не определено."Текст ранее ссылается на неопределенные байты, но это только потому, что байты не имеют представления ловушки.

Является ли это строгим сглаживанием нарушение?

Я не вижу в вашем коде никакого строгого нарушения алиасинга.

В то время как стандарт C позволяет операции, чтобы разбить заполняющие биты, я не думаю, что это то, что происходит в вашей системе. Скорее, они никогда не инициализируются с самого начала, и GCC просто оптимизирует memset в -O1, так как объект впоследствии перезаписывается. Это, вероятно, можно было бы подавить с помощью -fno-builtin-memset.

Я не вижу здесь ничего неопределенного или даже неопределенного (две очень разные вещи). Да, вызовы memset() оптимизированы. На моей машине (i86-32) long double-это 12 байт, дополненных до 16 в структурах и в стеке. На вашем компьютере они явно заполнены 16 байтами, так как sizeof(long double) возвращает 16. Ни один из выходов "printme" не похож на правильный 128-битный формат с плавающей запятой IEEE, поэтому я подозреваю, что в функции printme() есть и другие ошибки, которые здесь не показаны.