LINUX.ORG.RU

4.2 на хабре

 ,


0

4

В статье http://habrahabr.ru/post/267239/ автор наезжает на общепринятый в C и C++ трюк с использованием union, утверждая, что стандарт такое использование запрещает:

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

Есть кто с учёткой на хабре, напишите ему, что он нехороший человек. Цитата из стандарта:

If a standard-layout union contains two or more standard-layout structs that share a common initial sequence, and if the standard-layout union object currently contains one of these standard-layout structs, it is permitted to inspect the common initial part of any of them. Two standard-layout structs share a common initial sequence if corresponding members have layout-compatible types and either neither member is a bit-field or both are bit-fields with the same width for a sequence of one or more initial members.

Нельзя же так на популярном ресурсе делать, могут читать дети.

★★★★

Не, там все же есть 4.2

В частности

1)

Копирование регионов памяти — гарантированно безопасная операция. (далее идет пример с memcpy)

Фиг там. Если вы используете memcpy(не memmove) и вы им копируете память из пересекающихся регионов, вы получите UB.

2)

Корректное решение номер два — использование char*

Открываем http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-ali... и читаем

The converse is not true. Casting a char* to a pointer of any type other than a char* and dereferencing it is usually in volation of the strict aliasing rule.

Это написано относительно C, но в крестах я думаю будет то же самое.

Насчет использования union, тут скорее всего он прав.

SZT ★★★★★
()
Последнее исправление: SZT (всего исправлений: 1)
Ответ на: комментарий от SZT

1) Откуда там пересекающиеся регионы, копирование из одной переменной в другую. 2) там и написано: «с большими оговорками (на x86)».

anonymous
()
Ответ на: комментарий от i-rinat

Офигеть! Реально заменяет!

так, я сейчас не въехал, это шутка была или серьезно?
Для меня этот вопрос очень важен...

reprimand ★★★★★
()
Ответ на: комментарий от mkam

Посоны, а ткните меня в стандарт, где написано:
Во-первых, стандарт требует, чтобы тип char всегда занимал ровно 1 байт памяти.

1 байт на некоторых платформах не всегда 8 бит. На древних, например, это было 7 бит (вспоминаем 7-битные пританцовки).

Не просто так создали int8_t и uint8_t вместо signed char и unsigned char (см. stdint.h)

reprimand ★★★★★
()
Ответ на: комментарий от reprimand

это шутка была или серьезно?

Серьёзно. Просто для меня было неожиданно.

$ cat q.c
#include <stdio.h>
int main(void) {
  printf("hello\n");
  return 0;
}

$ gcc -c q.c
$ objdump -Sr q.o

q.o:     формат файла elf64-x86-64


Дизассемблирование раздела .text:

0000000000000000 <main>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	bf 00 00 00 00       	mov    $0x0,%edi
			5: R_X86_64_32	.rodata
   9:	e8 00 00 00 00       	callq  e <main+0xe>
			a: R_X86_64_PC32	puts-0x4
   e:	b8 00 00 00 00       	mov    $0x0,%eax
  13:	5d                   	pop    %rbp
  14:	c3                   	retq   
$ 

Ну и если убрать \n, то по смыслу заменить не удастся, что и видно:

$ cat q.c 
#include <stdio.h>
int main(void) {
  printf("hello");
  return 0;
}

$ gcc -g q.c
$ objdump -Sr q.o

q.o:     формат файла elf64-x86-64


Дизассемблирование раздела .text:

0000000000000000 <main>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	bf 00 00 00 00       	mov    $0x0,%edi
			5: R_X86_64_32	.rodata
   9:	b8 00 00 00 00       	mov    $0x0,%eax
   e:	e8 00 00 00 00       	callq  13 <main+0x13>
			f: R_X86_64_PC32	printf-0x4
  13:	b8 00 00 00 00       	mov    $0x0,%eax
  18:	5d                   	pop    %rbp
  19:	c3                   	retq   
$ 
i-rinat ★★★★★
()
Ответ на: комментарий от reprimand

И... что же теперь?..

А что, всё правильно делает. Если есть \n в конце, printf делает то же самое, что и puts, только медленнее.

Интересно, clang так же делает?

clang 3.5 с O0 не заменяет, с O1 и O2 уже заменяет.

i-rinat ★★★★★
()
Ответ на: комментарий от reprimand

1 байт на некоторых платформах не всегда 8 бит.

Я где-то утверждал обратное? Я просил указание на пункт стандарта, где написано, что char занимает 1 байт.

mkam
()
Ответ на: комментарий от anonymous

Откуда там пересекающиеся регионы, копирование из одной переменной в другую

Да ладно? А если из union в union копировать? Там как раз таки могут регионы пересекаться

SZT ★★★★★
()
Ответ на: комментарий от i-rinat

Офигеть! Реально заменяет!

Это хорошо, конечно, но вот некоторые функции, наоборот, неожиданно медленно работают. Например, getnameinfo() с указанием NI_NUMERICHOST и с указанием конкретного family работает в glibc ужасно медленно из-за внутренних printf()'ов. Ещё localtime_r() очень тормозная.

Sorcerer ★★★★★
()

С разморозкой. Там 4.2 в каждой второй статье, а в каждой третьей еще и вредные советы.

entefeed ☆☆☆
()
Ответ на: комментарий от reprimand

И... что же теперь?

Я ещё на gcc 4.7 с этим сталкивался, скорее всего, сделано это уже давно, брат жив. Оптимизация, вроде, логичная, никто не жалуется.

Gvidon ★★★★
() автор топика
Ответ на: комментарий от anonymous

Спасибо. Это проворонил. До сего момента ссылался на 3.9.1

mkam
()
Ответ на: комментарий от Legioner

Экхм. Ты уверен, что спрашиваешь того человека?

i-rinat ★★★★★
()

Ну к слову, я однажды на это (strict aliasing) сам наткнулся, когда при -O2 библиотека переставала работать адекватно. Я уж думал всё, компилятор глючит, полез гуглить баги gcc, и нашел вот это. А там написано следующее:

To fix the code above, you can use a union instead of a cast (note that this is a GCC extension which might not work with other compilers)

Так что для gcc можно считать union разрешенным и задокументированным способом решения проблемы strict aliasing.

Kiborg ★★★
()
Ответ на: комментарий от ilammy

Ну так то union. А там говорилось, что типа вместо union человеку следует написать вызов memcpy().

Если union используется для type punning(записываем нечто в один член union и читаем из другого), это одно. А если мы через mempcy копируем из одного члена union в другой, и регионы пересекаются, это другое. Во втором случае union не используется для type punning, просто тот факт, что мы через memcpy копируем память из пересекающихся регионов, создает UB. Это разные вещи

SZT ★★★★★
()
Ответ на: комментарий от i-rinat

На TMS320 серии C54/C55 sizeof(char) == sizeof(int), а размер байта 16 бит. Вот у меня на столе лежит платка, а в ящике еще одна и десяток чипов.

Puzan ★★★★★
()
Ответ на: комментарий от anonymous

На TMS320 серии C54/C55 sizeof(char) == sizeof(int), а размер байта 16 бит.

И чем это вызвано?

На DSP всё через жопу.

tailgunner ★★★★★
()
Ответ на: комментарий от anonymous

И что? Там написано

Копирование регионов памяти — гарантированно безопасная операция.

Это не гарантированно безопасная операция. Для копирования из члена union одного типа в тот же union на другой тип, memcpy использовать в общем случае нельзя. Лучше использовать memmove. Код в примере безопасен, но в общем случае подобный прием с memcpy не безопасен

SZT ★★★★★
()
Ответ на: комментарий от anonymous

И чем это вызвано?

Просто там 8 бит не нужно. Процессор и все его блоки заточены на математические вычисления над 16/32-разрядными числами. По этому там все выровнено по границе 16 бит (т.е. нельзя даже обратиться к произвольному байту в классическом понимании). А еще там 48-битный аккумулятор (два).

Puzan ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.