Когда char * безопасен для строгого сглаживания указателя?

Я пытаюсь понять строгие правила псевдонимов, применяемые к указателю char.

Здесь сказано:

Всегда предполагается, что char * может ссылаться на псевдоним любого объекта.

Хорошо, поэтому в контексте кода сокета я могу это сделать:

struct SocketMsg
{
   int a;
   int b;
};

int main(int argc, char** argv)
{
   // Some code...
   SocketMsg msgToSend;
   msgToSend.a = 0;
   msgToSend.b = 1;
   send(socket, (char*)(&msgToSend), sizeof(msgToSend);
};

Но тогда это утверждение

Обратное неверно. Выделение char * указателю любого типа, кроме char *, и разыменование его, как правило, нарушает правило строгого сглаживания.

Означает ли это, что когда я возвращаю массив char, я не могу переинтерпретировать приведение в структуру, когда знаю структуру сообщения:

struct SocketMsgToRecv
{
    int a;
    int b;
};

int main()
{
    SocketMsgToRecv* pointerToMsg;
    char msgBuff[100];
    ...
    recv(socket, msgBuff, 100);
    // Ommiting make sure we have a complete message from the stream
    // but lets assume msgBuff[0]  has a complete msg, and lets interpret the msg

    // SAFE!?!?!?
    pointerToMsg = &msgBuff[0];

    printf("Got Msg: a: %i, b: %i", pointerToMsg->a, pointerToMsg->b);
}

Не будет ли этот второй пример работать, потому что базовый тип - это массив char, и я передаю его структуре? Как вы справляетесь с этой ситуацией в строго псевдонимом мире?

Ответы

Ответ 1

Re @Adam Rosenfield: союз достигнет выравнивания, пока поставщик char * начал делать что-то подобное.

Может быть полезно отступить и выяснить, что это такое.

Основой правила aliasing является тот факт, что компиляторы могут размещать значения разных простых типов на разных границах памяти для улучшения доступа, и это оборудование в некоторых случаях может потребовать, чтобы такое выравнивание могло вообще использовать указатель. Это также может отображаться в структурах, где есть множество элементов разного размера. Структура может быть запущена на хорошей границе. Кроме того, компилятор может все же представить слабые укусы внутри структуры, чтобы выполнить правильное выравнивание элементов структуры, которые этого требуют.

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

Как насчет char *?

Презумпция char * заключается в том, что sizeof (char) == 1 (относительно размеров всех других значимых данных) и что указатели char * не имеют требования к выравниванию. Таким образом, подлинный char * всегда можно безопасно передавать и использовать без проблем для выравнивания, и это относится к любому элементу массива char [], выполняя ++ и - по указателям и т.д. (Как ни странно, void * не совсем то же самое.)

Теперь вы должны уметь видеть, как переносить какие-то данные структуры в массив char [], который не был сам выровнен соответствующим образом, попытка отбросить указатель, который требует выравнивания (s), может быть серьезная проблема.

Если вы создадите объединение массива char [] и структуры, наиболее сложное выравнивание (т.е. структуры) будет выполняться компилятором. Это будет работать, если поставщик и потребитель эффективно используют совпадающие союзы, так что отличное отведение структуры * до char * и обратно работает нормально.

В этом случае я хотел бы надеяться, что данные были созданы в аналогичном объединении до того, как указатель на него был перенесен на char * или он был перенесен любым другим способом в виде массива sizeof (char) байтов. Также важно убедиться, что любые параметры компилятора совместимы между используемыми библиотеками и вашим собственным кодом.

Ответ 2

Правильно, второй пример нарушает правила строгого сглаживания, поэтому, если вы скомпилируете флаг -fstrict-aliasing, вы можете получить неправильный код объекта. Полностью правильное решение было бы использовать здесь соединение:

union
{
  SocketMsgToRecv msg;
  char msgBuff[100];
};

recv(socket, msgBuff, 100);

printf("Got Msg: a: %i, b: %i", msg.a, msg.b);