Блок кода, который должен вызвать ошибку шины, выполняется нормально

В настоящее время я читаю Expert C Programmign - Deep C Secrets. На странице 164, где автор объяснял ошибку шины и ошибку сегментации, он показал эту строку кода.

union { 
  char a[10];
  int i;
} u ;

int * p = ( int * ) &(u.a[1]);
*p = 17; /* the misaligned addr in p causes a bus error */

Предполагается, что приведенный выше код вызывает ошибку шины, но когда я запустил его, он работал нормально без каких-либо ошибок. Автор дал следующее пояснение

Это вызывает ошибку шины, потому что объединение массива/целого числа гарантирует, что массив символов «а» также находится по разумно выровненному адресу для целого числа, поэтому «а+1» определенно нет. Затем мы пытаемся сохранить 4 байта в адресе, который выровнен только для однобайтового доступа. Хороший компилятор предупредит о неправильном выравнивании, но он не может определить все случаи.

Мое понимание приведенного выше утверждения состоит в том, что char составляет 1 байт, и мы пытаемся поместить int, который составляет 4 байта, в индекс char a[10], поэтому произойдет ошибка шины (я не уверен, что мое понимание правильное или неправильное)

Мой вопрос в том, почему приведенный выше код не вызывает ошибку шины.

Примечание. Я не изучаю информатику, поэтому помогут простые объяснения.

Примечание. Вопрос, похожий на этот вопрос, уже задавался, но я касаюсь только приведенного выше блока кода.


person 0.sh    schedule 24.12.2018    source источник
comment
У меня есть книга, в которой говорится, что мне нельзя ехать со скоростью 100 км/ч в зоне 30 км/ч. Но я все равно сделал это, и меня не арестовали. Почему меня не арестовали?   -  person HAL9000    schedule 24.12.2018
comment
Я мог бы увидеть ошибку шины и segfault гораздо более реалистично, если бы a был указателем (например, char *a;). В любом случае у вас есть i, который занимает примерно 1/2 доступного хранилища, но если вы изменили i в этом случае на значение за пределами вашего действительного блока памяти - ошибка шины/segfault.   -  person David C. Rankin    schedule 24.12.2018
comment
Какую архитектуру и ОС вы используете? Если вы используете Intel, вы не заметите никакой разницы (в худшем случае на несколько наносекунд медленнее для дополнительной выборки памяти). Если вы используете ARM/PowerPC/MIPS и т. д. с Linux, вы, вероятно, обнаружите, что ядро ​​настроено на автоматическое исправление. Посмотрите /proc/cpu/alignment для получения подробной информации о том, как управлять поведением ядра и просматривать количество исправлений.   -  person Dipstick    schedule 24.12.2018
comment
Так много неосведомленных ответов и комментариев по этому поводу...   -  person fuz    schedule 25.12.2018


Ответы (3)


Я считаю, что книга ошибается. Код вызывает неопределенное поведение. Поэтому ожидать от него какого-либо определенного поведения ошибочно. Также обратите внимание, что не все архитектуры могут вызывать ошибки шины. Если книга не объясняет этот факт, это тоже ничего не говорит.

person Ulrich Eckhardt    schedule 24.12.2018
comment
Да, это тот случай, когда сжигание книг допустимо. - person Martin James; 24.12.2018
comment
@MartinJames - это расточительно. Но всегда можно обойтись ломом/туалетной бумагой. - person StoryTeller - Unslander Monica; 24.12.2018
comment
Книга не обязательно ошибочна, но может быть неполной. Это не проблема для большинства архитектур ЦП Intel (которые решают проблему аппаратно), но существует во многих архитектурах 32-разрядных ЦП, используемых во встроенных системах, например. ARM, MIPS, PowerPC. Часто ядро ​​ОС (например, Linux) может быть настроено так, чтобы обнаруживать ошибку и выполнять исправление. Это очень неэффективно, но, вероятно, приемлемо для большинства пользовательских приложений. Накладные расходы неприемлемы для приложений и драйверов реального времени, и IIRC ядро ​​Linux не исправляет ошибки, вызванные самим ядром. - person Dipstick; 24.12.2018
comment
Может быть, @Dipstick. Суть в том, вызывает ли код неопределенное поведение или нет. Если код вызывает UB, а книга предполагает, что это надежно вызывает ошибку шины, даже с подразумеваемым предположением о том, что процессор их обнаруживает, то книга, безусловно, ошибается. Я не исключаю, что перекрестное чтение и выборочное цитирование делают это похоже на ошибку в книге. - person Ulrich Eckhardt; 24.12.2018
comment
@Ulrich Eckhardt - Книга начала 1990-х годов описывает архитектуру Sun SPARC и упоминает, в частности, архитектуры RISC в связи с проблемами выравнивания памяти. Книгу нужно сжечь из-за возраста, но вопросы по-прежнему очень актуальны. Также не очень полезно говорить, что поведение не определено; часто используется другой код в зависимости от компилируемой архитектуры. Ядро Linux использует для этого #define CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS. - person Dipstick; 24.12.2018
comment
Комментарии, говорящие вам вытереть задницу этой книгой, а затем сжечь ее (предположительно в таком порядке), немного резки. Лучше было бы сказать, что вы можете разумно ожидать, что ваша программа рухнет в этот момент. Я считаю, что процессор x86 позволил бы этому скользить (за счет производительности). Я уверен, что 8080 будет. Однако ответ Ульриха правильный. Неопределенное поведение часто проявляется здесь есть драконы. В этот момент системе разрешено делать все, включая успех. - person Edward Falk; 24.12.2018
comment
@Dipstick, да, компьютер Sun получил бы здесь ошибку шины. Если эта книга была конкретно о Солнцах, то это правильно. Для процессоров 680x0 требуется 16-битное выравнивание, а для Sparc — 32-битное. На самом деле я был в Sun во время перехода между 680x0 и Sparc, и в то время было обнаружено и исправлено множество ошибок выравнивания. - person Edward Falk; 24.12.2018

Мое понимание приведенного выше утверждения состоит в том, что char равен 1 байту, и мы пытаемся поместить целое число, равное 4 байтам, в индекс char a[10], поэтому произойдет ошибка шины (я не уверен, правильно ли я понимаю или неправильно )

Проблема не в размере char или int, а в их выравнивании. Как правило, архитектуры привередливы в отношении адресов, с которых вы загружаете данные/код, например, вы можете загрузить 16-битное целое число только с адреса, кратного 16-битному, или функция всегда должна начинаться с 4-байтового адреса. граница.

Если вы этого не сделаете, процессор может считать неверные данные или наказать вас исключением. Затем ОС может эмулировать его, используя несколько выровненных доступов, или передать его как SIGBUS пользовательскому приложению. Последнее является тем, что автор, вероятно, испытывает на своей установке.

Как все это связано с C?

У вас есть неопределенное поведение. Взаимодействие процессора, контроллера памяти, компилятора, ОС и носовых демонических отродий в вашем районе будет влиять на то, какой эффект это имеет (если вообще). Возможно, на вашем компьютере процессор изначально поддерживает невыровненный доступ, поэтому он работал, но все же на это нельзя полагаться: он просто не определен. (Особенно с оптимизацией эти вещи могут обернуться против вас. Works for me™ недостаточно хорош для написания четко определенного кода на языке C!)

person a3f    schedule 24.12.2018

Данные, которые вы пытаетесь извлечь, выходят за 32-битную границу, поэтому требуется 2 выборки памяти (но компилятор не обязательно знает об этом во время компиляции).

Примечание. Книга очень старая и говорит о 32-битных процессорах. Для 64-битной версии вам, возможно, придется изменить int * p = ( int * ) &(u.a[1]); на int * p = ( int * ) &(u.a[5]);, чтобы все необходимые данные не могли быть получены за одну выборку памяти с выровненного адреса.

По соображениям обратной совместимости большинство процессоров Intel (и производных, таких как AMD) автоматически обрабатывают ошибки выравнивания памяти на уровне инструкций, поэтому вы не заметите ничего неправильного (упрощенно он автоматически добавляет дополнительное чтение, блокируя шину, чтобы убедиться, что между чтениями ничего не изменилось). ).

На многих других архитектурах ЦП (ARM, PowerPC, MIPS, более новые архитектуры Intel) пример кода вызвал бы проблемы, как описано, но теперь некоторые операционные системы, такие как Linux, можно настроить для автоматического обнаружения ошибки и выполнения «исправления», позволяющего программа для продолжения, не подозревая, что возникла проблема. С большинством программ это, вероятно, останется незамеченным пользователем, но требует много времени и вызовет реальные проблемы с программным обеспечением и драйверами в реальном времени.

Критический по времени код часто условно компилируется, чтобы выполнять или не выполнять невыровненный доступ в соответствии с архитектурой ЦП, для которой он компилируется. В Linux псевдофайл «/proc/cpu/alignment» может использоваться для управления поведением ядра и просмотра статистики о количестве «исправлений».

person Dipstick    schedule 24.12.2018
comment
Обратите внимание, что в ARM на самом деле необязательно поддерживать невыровненные нагрузки, а ARMv8 фактически требует поддержки невыровненных нагрузок, насколько мне известно. - person fuz; 25.12.2018