выравнивание памяти в структурах gcc - PullRequest
12 голосов
/ 31 марта 2010

Я портирую приложение на платформу ARM в C, приложение также работает на процессоре x86 и должно иметь обратную совместимость.

У меня сейчас есть некоторые проблемы с выравниванием переменных. Я прочитал руководство по gcc для __attribute__((aligned(4),packed)) Я интерпретирую то, что говорится, поскольку начало структуры выровнено по 4-байтовой границе, а внутренняя часть остается нетронутой из-за упакованного оператора.

Первоначально у меня было это, но иногда оно помещается без выравнивания с 4-байтовой границей.

typedef struct  
{  
 unsigned int code;  
 unsigned int length;  
 unsigned int seq;  
 unsigned int request;  
 unsigned char nonce[16];  
 unsigned short  crc;  
} __attribute__((packed)) CHALLENGE;

так что я изменил это на это.

typedef struct  
{  
 unsigned int code;  
 unsigned int length;  
 unsigned int seq;  
 unsigned int request;  
 unsigned char nonce[16];  
 unsigned short  crc;  
} __attribute__((aligned(4),packed)) CHALLENGE;

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

Не могли бы вы описать мне, как выполнить нужную мне операцию? Любая помощь очень ценится.

Ответы [ 6 ]

11 голосов
/ 31 марта 2010

Если вы полагаете, что sizeof(yourstruct) составляет 42 байта, вы будете укушены миром непереносимых предположений. Вы не сказали, для чего это нужно, но похоже, что и порядок элементов структуры содержимого также имеет значение, поэтому у вас также может быть несоответствие с x86.

В этой ситуации я думаю, что единственный верный способ справиться с ситуацией - это использовать unsigned char[42] в тех частях, где это имеет значение. Начните с написания точной спецификации того, какие именно поля находятся в этом 42-байтовом блоке и какой порядок байтов, а затем используйте это определение, чтобы написать некоторый код для преобразования между ним и структурой, с которой вы можете взаимодействовать. Код, скорее всего, будет либо последовательным кодом сериализации (он же маршаллинг), либо набором методов получения и установки.

5 голосов
/ 31 марта 2010

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

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

Я полагаю, у вас есть что-то вроде:

read(fd, &obj, sizeof obj)

Поскольку вы не хотите читать те 2 байта заполнения, которые принадлежат разным данным, вы должны явно указать размер:

read(fd, &obj, 42)

Который вы можете поддерживать:

typedef struct {
  //...
  enum { read_size = 42 };
} __attribute__((aligned(4),packed)) CHALLENGE;

// ...

read(fd, &obj, obj.read_size)

Или, если вы не можете использовать некоторые функции C ++ в вашем C:

typedef struct {
  //...
} __attribute__((aligned(4),packed)) CHALLENGE;
enum { CHALLENGE_read_size = 42 };

// ...

read(fd, &obj, CHALLENGE_read_size)

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

2 голосов
/ 31 марта 2010

Какова ваша истинная цель?

Если вы имеете дело с данными, которые находятся в файле или на проводе в определенном формате, вам нужно записать некоторые процедуры маршалинга / сериализации, которые перемещают данные между структурой компилятора, которая представляет, как вы хотите работать с данные внутри программы и массив символов, который имеет отношение к тому, как данные выглядят в проводе / файле.

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

1 голос
/ 30 августа 2018

Я перемещал структуры назад и вперед из Linux, Windows, Mac, C, Swift, Assembly и т. Д.

Проблема НЕ в том, что это невозможно, проблема в том, что вы не можете быть ленивым и должны понимать свои инструменты.

Я не понимаю, почему вы не можете использовать:

typedef struct  
{  
 unsigned int code;  
 unsigned int length;  
 unsigned int seq;  
 unsigned int request;  
 unsigned char nonce[16];  
 unsigned short  crc;  
} __attribute__((packed)) CHALLENGE;

Вы можете использовать его, и он не требует специального или умного кода. Я пишу много кода, который общается с ARM. Структуры - это то, что заставляет вещи работать. __attribute__ ((packed)) мой друг.

Вероятность оказаться в "мире боли" равна нулю, если вы понимаете, что происходит с обоими.

Наконец, я не могу на всю жизнь понять, как вы получаете 42 или 44. Int - это либо 4 наших 8 байта (в зависимости от компилятора). Это ставит число в 16 + 16 + 2 = 34 или 32 + 16 + 2 = 50 - при условии, что оно действительно упаковано.

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

0 голосов
/ 01 апреля 2010

Поскольку я использую Linux, я обнаружил, что к echo 3 > /proc/cpu/alignment он выдаст мне предупреждение и решит проблему с выравниванием. Это обходной путь, но он очень полезен для определения того, где структуры не могут быть смещены.

0 голосов
/ 31 марта 2010

Я предполагаю, что проблема в том, что 42 не делится на 4, и поэтому они теряют выравнивание, если вы помещаете несколько таких структур вплотную (например, выделяете память для нескольких из них, определяя размер с помощью sizeof). Если размер равен 44, выровняйте выравнивание в этих случаях, как вы и просили. Однако, если внутреннее смещение каждого элемента структуры остается неизменным, вы можете обработать 44-байтовую структуру, как если бы она составляла 42 байта (при условии, что вы заботитесь о выравнивании любых следующих данных на правильной границе).

Один из возможных приемов - поместить обоих этих структур в один тип объединения и использовать только 42-байтовую версию из каждого такого объединения.

...