Когда char * безопасен для строгого наложения указателей? - PullRequest
23 голосов
/ 04 ноября 2008

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

Здесь это заявлено:

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

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

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 *, и разыменование его, как правило, нарушают правило строгого наложения имен.

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

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);
}

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

Ответы [ 2 ]

8 голосов
/ 04 ноября 2008

Ре @ Адам Розенфилд: Союз добьется выравнивания до тех пор, пока поставщик персонажа * не начнет делать что-то подобное.

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

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

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

А как насчет символа *?

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

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

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

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

6 голосов
/ 04 ноября 2008

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

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

recv(socket, msgBuff, 100);

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