12

При компиляции такого кода для ARM

char* c = 0;
int* p = reinterpret_cast<int*>(c);

GCC выводит предупреждение

cast from ‘char*’ to ‘int*’ increases required alignment of target type

Какими последствиями чревато подобное применение reinterpret_cast для типов с разными требованиями по выравниванию? Почему такое предупреждение возникает не на всех платформах? О чём вообще это, нужно ли как-то исправлять?

https://godbolt.org/g/VGDK3w

По ссылке выше вы увидете попытку компиляции такого кода для процессоров x86_64 и ARM. Несмотря на одинаковые флаги компилятора, результат получился разным.

mymedia
  • 8,602
  • int - это signed. char - это unsigned. И.е. int может иметь отрицательную знаковую часть. Это предупреждение, чтобы программист помнил это. – Mister_Jesus Jul 14 '18 at 16:02
  • 2
    Это чревато исключением (на уровне аппаратуры -- exception) при доступу по указателю p (впрочем, зависит от архитектуры). Вообще это означает, что данные к которым вы обращаетесь могут находится по адресу не кратном размеру адресуемого элемента данных – avp Jul 14 '18 at 16:03
  • @MrBin, знаковый char приводит к аналогичному предупреждению - https://godbolt.org/g/kaEvR5 – mymedia Jul 14 '18 at 16:04
  • Ну, я обозначил самою очевидную вещь. Есть ещё переполнение и смеси всего. И это без особенностей платформ. – Mister_Jesus Jul 14 '18 at 16:08
  • 2
    @MrBin, вы не правы. Речь о выравнивании данных в памяти – avp Jul 14 '18 at 16:09
  • @avp, тогда остаётся только переполнение. А так 8 бит одного типа и 8 бит другого будет работать одинаково. Но только если одинаковое их количество) – Mister_Jesus Jul 14 '18 at 16:12
  • @MrBin, вот эти слова increases required alignment of target type в тексте вопроса вы видели? – avp Jul 14 '18 at 16:14
  • @avp, перечитайте лучше мой последний комментарий. Тем более уже дан развернутый ответ. – Mister_Jesus Jul 14 '18 at 16:16
  • Это UB, но я не возьмусь сейчас выискивать все релевантные цитаты из стандарта. Но начать можно со strict aliasing. P.S. avp всё правильно говорит. – ixSci Jul 14 '18 at 16:17
  • @MrBin, и где в сообщении компилятора вы увидели что-то о переполнении? – avp Jul 14 '18 at 16:21
  • Не совсем понятно замечание про "результат получился разным". Вас удивляет, что машинный код для двух разных процессоров отличается? – AnT stands with Russia Jul 14 '18 at 16:21
  • @AnT, нуу... для ARM там вообще никакого кода не получилось. – mymedia Jul 14 '18 at 16:22
  • @mymedia: Ну так вы запросили прерывание генерации кода для нестандартного диагностического сообщения. Нестандартные диагностические сообщения они такие - сегодня есть, завтра нет, в одном месте есть, в другом нет... – AnT stands with Russia Jul 14 '18 at 16:24
  • @avp, я спорить не собираюсь. Но. Я привет простой пример, когда можно безвозмездно приводить типы (Примерах хорошего приведения). Логично можно было подумать, что приведение char к большему разряду int будет примером плохого приведения. Указатель на 8 к указателю на (16-32-64) и вуаля. UB. Так что советую ещё раз перечитать) А вы придираетесь к слову переполнение. – Mister_Jesus Jul 14 '18 at 16:27
  • @MrBin: "char - это unsigned". Почему вы решили, что это именно так? – AnT stands with Russia Jul 14 '18 at 16:30
  • @MrBin,о чем вы вообще говорите? Я о конкретном вопросе автора -- Какими последствиями чревато подобное применение reinterpret_cast для типов с разными требованиями по выравниванию?. Если говорить о конкретном коде в тексте вопроса, то независимо от предупреждения ничего плохого ни в ARM, ни на X86 не произойдет, поскольку обращения к памяти по указателям в нем нет – avp Jul 14 '18 at 16:32
  • С каких пор char стал unsigned? – NewView Jul 14 '18 at 17:01
  • @NewView, строго говоря, знаковость char зависит от реализации. Вот в GCC это даже можно настроить - https://stackoverflow.com/a/20518559/5000805 – mymedia Jul 14 '18 at 17:11
  • Да GCC/clang чего угодно настроить можно, согласен :) а вот реализация где char по умолчанию unsigned? не разу не сталкивался с таким. – NewView Jul 16 '18 at 09:51

1 Answers1

13

Поведение reinterpret_cast в этом случае зависит от платформы. Начиная с С++11 поведение такого преобразования определяется как

int* p = static_cast<int *>(static_cast<void *>(c));

и результат будет зависеть от поведения внешнего static_cast. Поведение static_cast из void * в объектный указательный тип является неспецифицированным, если исходный void * адрес не удовлетворяет требованиям выравнивания целевого типа.

  • На одной платформе приведение типа от указателя с более расслабленными требованиями выравнивания к указателю с более строгими требованиями выравнивания может привести к потере оригинального адресного значения, т.е. компилятор и/или аппаратура насильно выравняют указатель.

  • На другой платформе адресное значение указателя сохранится, но при попытке доступа через невыровненный указатель программа будет аварийно остановлена.

  • На третьей платформе адресное значение указателя сохранится, но при доступе через невыровненный указатель такой доступ будет производиться успешно, но менее эффективно.

  • На четвертой платформе не будет вообще никаких негативных последствий.

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

  • Насколько мне известно, reinterpret_cast не вызывает каких-либо преобразований времени выполнения, это чисто для компилятора. А вот второй случай уже интереснее, как бы такое падение воспроизвести? – mymedia Jul 14 '18 at 16:24
  • 1
    @mymedia: В спецификации reinterpret_cast не сказано, что он "не вызывает каких-либо преобразований времени выполнения, это чисто для компилятора". Как раз наоборот, там сказано, что круговое преобразование T* -> U* -> T* не гарантирует сохранения исходного значения указателя, если тип U имеет более строгие требования выравнивания. Это именно задел на возможность потери значения в таких ситуациях. А "преобразования" могут быть жестко заложены в аппаратуру. – AnT stands with Russia Jul 14 '18 at 16:27
  • 1
    @mymedia: Падения во втором случае будут воспроизводиться на платформах с жесткими требованиями выравнивания, типа Solaris. При доступе сразу получите Bus Error. – AnT stands with Russia Jul 14 '18 at 16:28
  • 2
    @mymedia, найдите код, который компилятор векторизует (это не так сложно, можно сумму простую сделать и поизучать код). Потом переделайте его так, чтобы получился массив с невыровненными int. Таким образом получите исключение даже на x86. – ixSci Jul 14 '18 at 16:34
  • Доступ по невыровненному указателю называется "unaligned access". Из широко распространённых процессоров невозможностью unaligned access страдали ARM7, ARM9 - при попытке чтения 32-битного значения по адресу, некратному четырём, случался exception. Более новые ARM'ы (Cortex -A, -M, -R) в этом случае корректно читают значение (правда, при этом производится пара обращений к памяти вместо одного) – Alexey Esaulenko Jul 14 '18 at 19:24
  • Так и не понял, нужно ли исправлять такое предупреждение или можно его проигнорировать. – mymedia Nov 11 '18 at 10:32