Правильно ли он определен для удержания смещенного указателя, если вы никогда не разыменовываете его? - PullRequest
0 голосов
/ 06 июля 2018

У меня есть некоторый код C, который анализирует упакованные / неупакованные двоичные данные, поступающие из сети.

Этот код был / работает нормально под Intel / x86, но когда я компилировал его под ARM, он часто зависал.

Виновником, как вы могли догадаться, были невыровненные указатели - в частности, код синтаксического анализа будет делать такие сомнительные вещи, как это:

uint8_t buf[2048];
[... code to read some data into buf...]
int32_t nextWord = *((int32_t *) &buf[5]);  // misaligned access -- can crash under ARM!

... очевидно, что он не будет летать на ARM-земле, поэтому я изменил его так, чтобы он выглядел так:

uint8_t buf[2048];
[... code to read some data into buf...]
int32_t * pNextWord = (int32_t *) &buf[5];
int32 nextWord;
memcpy(&nextWord, pNextWord, sizeof(nextWord));  // slower but ARM-safe

Мой вопрос (с точки зрения юриста по языку) таков: хорошо ли определен мой "ARM-фиксированный" подход в правилах языка C?

Меня беспокоит то, что, возможно, даже просто иметь неправильный указатель int32_t может быть достаточно для вызова неопределенного поведения, даже если я никогда не разыменовываю его напрямую. (Если моя проблема верна, думаю, я мог бы решить эту проблему, изменив тип pNextWord с (const int32_t *) на (const char *), но я бы предпочел этого не делать, если это на самом деле не нужно, поскольку это значит делать ручную арифметику с указателем шага)

Ответы [ 4 ]

0 голосов
/ 11 июля 2018

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

void copy_uint32(uint32_t *dest, uint32_t *src)
{
  memcpy(dest, src, sizeof (uint32_t));
}

Если и dest, и src содержат 32-разрядные выровненные адреса, указанную выше функцию можно оптимизировать для одной загрузки и одного хранилища даже на платформах, которые не поддерживают несогласованный доступ. Однако, если бы функция была объявлена ​​для приема аргументов типа void*, такая оптимизация не была бы разрешена на платформах, где не выровненные 32-битные обращения будут вести себя иначе, чем последовательность байтовых обращений, сдвигов и побитовых операций.

0 голосов
/ 06 июля 2018

Для безопасного анализа многобайтового целого числа по компиляторам / платформам, Вы можете извлечь каждый байт и собрать их в целое число в соответствии с порядком байтов. Например, чтобы прочитать 4-байтовое целое число из буфера с прямым порядком байтов:

uint8_t* buf = any address;

uint32_t val = 0;
uint32_t  b0 = buf[0];
uint32_t  b1 = buf[1];
uint32_t  b2 = buf[2];
uint32_t  b3 = buf[3];

val = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
0 голосов
/ 06 июля 2018

Как упоминалось в ответе Антти Хаапала, простое преобразование указателя в другой тип, когда результирующий указатель не выровнен должным образом, вызывает неопределенное поведение в соответствии с разделом 6.3.2.3p7 стандарта C.

Ваш модифицированный код использует только pNextWord для перехода к memcpy, где он преобразуется в void *, поэтому вам даже не нужна переменная типа uint32_t *. Просто передайте адрес первого байта в буфере, с которого вы хотите прочитать, на memcpy. Тогда вам вообще не нужно беспокоиться о выравнивании.

uint8_t buf[2048];
[... code to read some data into buf...]
int32_t nextWord;
memcpy(&nextWord, &buf[5], sizeof(nextWord));
0 голосов
/ 06 июля 2018

Нет, новый код по-прежнему имеет неопределенное поведение. C11 6.3.2.3p7 :

  1. Указатель на тип объекта может быть преобразован в указатель на другой тип объекта. Если полученный указатель неправильно выровнен 68) для ссылочного типа, поведение не определено. [...]

Это ничего не говорит о разыменовании указателя - даже преобразование имеет неопределенное поведение.


Действительно, измененный код, который вы предполагаете, является ARM -безопасным, может быть даже Intel -безопасным. Известно, что компиляторы генерируют код для Intel, который может аварийно завершить работу при неприсоединенном доступе . Хотя это не относится к связанному случаю, может случиться так, что умный компилятор может принять преобразование как доказательство , что адрес действительно выровнен, и использовать специальный код для memcpy.


Помимо выравнивания, ваш первый отрывок также страдает от строгого нарушения псевдонимов. C11 6,5p7 :

  1. Объект должен иметь свое сохраненное значение, доступное только через выражение lvalue, которое имеет один из следующих типов: 88)
    • тип, совместимый с эффективным типом объекта,
    • квалифицированная версия типа, совместимого с эффективным типом объект,
    • тип, который является типом со знаком или без знака соответствующий эффективному типу объекта,
    • тип, который тип со знаком или без знака, соответствующий квалифицированной версии эффективный тип объекта,
    • агрегатный или объединенный тип который включает в себя один из вышеупомянутых типов среди своих членов (включая, рекурсивно, член субагрегата или содержит союз), или
    • тип символа.

Поскольку массив buf[2048] статически типизирован , каждый элемент равен char, и, следовательно, эффективные типы элементов char; Вы можете получить доступ к содержимому массива только в виде символов, а не int32_t s.

То есть, даже

int32_t nextWord = *((int32_t *) &buf[_Alignof(int32_t)]);

имеет неопределенное поведение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...