Статья на слабую тройку. Перечислено только около 10% типичных ошибок программистов. Автор похоже коммитит код в NetBSD и наивно считает ее самой портабельной, да и еще незаслуженно обижает разработчиков под Linux, которая BTW поддерживает в стабильной ветке на 6 CPU архитектур больше чем NETBSD и как минимум в два раза больше платформ и MMU BSP.
----------------------------------------------------
Недопонимание переводчика:О приведении типов указателей
В главе о приведении типов указателей попалась одна непонятная фраза: You are allowed to cast to the ``right'' type of an object when assigning via a pointer, so the compiler assumes all variables of the casted-to type might have changed. Вот комментарии автора по этому поводу:
<<Скажем, у вас есть поток байтов, который включает в себя некоторый участок, и вы хотите обратиться к этому участку, как к структуре. Поток байтов не имеет выравнивания. Для указателя на байт используется char* или void*. Теперь, если привести тип указателя к типу struct* (указатель на вашу структуру), компилятор будет считать указатель допустимым, включая все требования по выравниванию, относящиеся к указателям на вашу структуру.
Это также справедливо в случае, если вы сперва выполнили приведение типа указателя, а потом использовали memcpy() для копирования содержимого структуры.
Однако, если вы выполните memcpy(), т.е. скопируете данные по указателю с неприведённым типом void*/char* в локальную типизированную структуру, то компилятор не станет делать никаких предположений и всё будет работать нормально.>>
Этот комментарий не до конца прояснил смысл фразы. Более понятно стало после комментария от gena2x. Речь идёт об особенностях оптимизации. Был приведён следующий пример:
$ cat > test.c
#include <stdio.h>
void f(int *i, long *l) {
printf("1.v=%ld\n", *l);
*i = 11;
printf("2.v=%ld\n", *l);
}
int main() {
long b = 10;
f((int*)&b, &b);
printf("3.v=%ld\n", b);
}
$ gcc -O2 test.c -o test
$ ./test
1.v=10
2.v=10
3.v=11
Неожиданное значение ``2. v=10''. Обратите внимание, при компиляции использовалась оптимизация -O2. Если оптимизацию не использовать, то вывод будет другим:
$ gcc test.c -o test
$ ./test
1.v=10
2.v=11
3.v=11
При использовании оптимизации компилятор ``не заметил'', что значение *l изменилось. Если же строку ``*i = 11;'' заменить на ``*(long *)i = 11;'', то компилятор сделает предположение, что могли измениться все значения по указателям, имеющим тип long * (тип к которому приводили), и соответственно при использовании *l будет получено уже новое значение (``2.v=11'').
----------------------------------------------------------------------------
Автор перевода и gena2x жгут...
Как можно перепутать обращение к невыравненным данным (по их размеру) с проблеммой strict алиасинга в (я бы посоветовал gena2x попробовать свой тест на ветке 2.x.x - 3.x.x например )?
gcc-3.3.6 -O3 test.c
1.v=10
2.v=11
3.v=11
Результат правильный... На других 4.x.x в основном неправильный...
А если так gcc -O3 -fno-strict-aliasing test.c
то всегда то что ожидалось :)
Да вы правильно догадались начиная с 4.x.x -O2 подразумевает -fstrict-aliasing , что дает инструкцию компилятору считать что все указатели разных типов указывают на непересекающиеся области памяти.
Таким образом рекомендуется при использовании оптимизации в GCC обязательно указывать флаг: -Wstrict-aliasing=2 тогда все проблемные места будет сразу видно:
$ gcc -O3 test.c -Wall -Wstrict-aliasing=2
test.c: In function ‘main’:
test.c:11: warning: dereferencing type-punned pointer will break strict-aliasing rules
Мои CFLAGS обычно включают седующие флаги:
-Wall -Wstrict-prototypes -Wcast-qual -Wcast-align -Wbad-function-cast -Wconversion -Wstrict-aliasing=2