связи 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()есть и другие ошибки, которые здесь не показаны.