связи 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 исчезнет. Это, кажется, зависит от двух факты:
- long double дополняется, то есть sizeof(long double) = 16, но используется только 10 байт.
- вызов memset может быть оптимизирован
- биты заполнения длинных двойников могут меняться без предупреждения, т. е. операции с плавающей запятой на значение1 и значение2, похоже, скремблируют биты заполнения.
Я компилирую с -std=c99 -Wall -Wextra -Wpedantic
и не получаю никаких предупреждений, поэтому я не уверен, что это случай нарушения строгого псевдонимирования (но это вполне может быть). Передача -fno-strict-aliasing
не делает изменить что-то.
Контекст-это ошибка, найденная в библиотеке HDF5, описанной здесь. HDF5 делает некоторую битную манипуляцию, чтобы выяснить собственное битовое представление типов с плавающей запятой, но он запутывается, если биты заполнения не остаются нулевыми.
Итак:
- является ли это неопределенным поведением?
- является ли это нарушением строгого сглаживания?
Спасибо.
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 ответа:
Является ли это неопределенным поведением?
Да. Биты заполнения неопределенны (*). Доступ к неопределенной памяти может также быть неопределенным поведением (это было неопределенное поведение в C90, и некоторые компиляторы C99 трактуют его как неопределенное поведение. Кроме того, в обосновании C99 говорится, что доступ к неопределенной памяти должен быть неопределенным поведением. Но сам стандарт C99 не говорит об этом так ясно, он только намекает на ловушки представлений и может произвести впечатление что если человек знает, что у него нет представлений ловушек, он может получить неопределенные значения из неопределенной памяти). Заполняющая Часть
В сноске 271 К99 говорится: "содержание " отверстий", используемых в качестве подкладки для выравнивания внутри объектов структуры, не определено."Текст ранее ссылается на неопределенные байты, но это только потому, что байты не имеют представления ловушки.long double
, по крайней мере, не определена.Является ли это строгим сглаживанием нарушение?
Я не вижу в вашем коде никакого строгого нарушения алиасинга.
В то время как стандарт C позволяет операции, чтобы разбить заполняющие биты, я не думаю, что это то, что происходит в вашей системе. Скорее, они никогда не инициализируются с самого начала, и GCC просто оптимизирует
memset
в-O1
, так как объект впоследствии перезаписывается. Это, вероятно, можно было бы подавить с помощью-fno-builtin-memset
.
Я не вижу здесь ничего неопределенного или даже неопределенного (две очень разные вещи). Да, вызовы
memset()
оптимизированы. На моей машине (i86-32) long double-это 12 байт, дополненных до 16 в структурах и в стеке. На вашем компьютере они явно заполнены 16 байтами, так какsizeof(long double)
возвращает 16. Ни один из выходов "printme" не похож на правильный 128-битный формат с плавающей запятой IEEE, поэтому я подозреваю, что в функцииprintme()
есть и другие ошибки, которые здесь не показаны.