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

Я потратил несколько минут на повторное упорядочивание полей в структуре, чтобы уменьшить эффекты отступов [1], что слишком много похоже на несколько минут. Чувство моего чувства говорит, что мое время, вероятно, лучше потратить на составление сценария Perl или на то, что для меня это не нужно.

Мой вопрос заключается в том, является ли это слишком избыточным; есть ли уже какой-то инструмент, о котором я не знаю, или какая-то функция компилятора, которую я должен включить [2], чтобы упаковать структуры?

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

EDIT: быстрое уточнение – то, что я хочу сделать, – это переупорядочить поле в исходном коде, чтобы избежать заполнения, а не «упаковать» структуру, которая компилируется без заполнения.

EDIT # 2: Еще одно осложнение: в зависимости от конфигурации могут изменяться и размеры некоторых типов данных. Очевидными являются указатели и указатели-diff для разных архитектур, но также типы с плавающей запятой (16, 32 или 64-бит в зависимости от «точности»), контрольные суммы (8 или 16 бит в зависимости от «скорости») и некоторые другие неочевидные вещи.

[1] Рассматриваемая структура создается тысячи раз на встроенном устройстве, поэтому каждое 4-байтное сокращение структуры может означать разницу между ходом и отсутствием для этого проекта.

[2] Доступными компиляторами являются GCC 3. * и 4. *, Visual Studio, TCC, ARM ADS 1.2, RVCT 3. * и еще несколько неясных.

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

Таким образом, человек почти наверняка победит робота на этой работе.

[Edit] Но похоже, что вы действительно не хотите вручную оптимизировать свои структуры для каждой архитектуры. Может быть, у вас действительно есть много архитектур для поддержки?

Я действительно думаю, что эта проблема не поддается общему решению, но вы можете кодировать знания своего домена в пользовательский скрипт Perl / Python / something, который генерирует определение структуры для каждой архитектуры.

Кроме того, если все ваши члены имеют размеры, равные двум, то вы получите оптимальную упаковку, просто отсортировав элементы по размеру (поначалу самый большой). В этом случае вы можете просто использовать хорошее старомодное структурирование на основе макросов – что-то вроде этого:

#define MYSTRUCT_POINTERS \ Something* m_pSomeThing; \ OtherThing* m_pOtherThing; #define MYSTRUCT_FLOATS \ FLOAT m_aFloat; \ FLOAT m_bFloat; #if 64_BIT_POINTERS && 64_BIT_FLOATS #define MYSTRUCT_64_BIT_MEMBERS MYSTRUCT_POINTERS MYSTRUCT_FLOATS #else if 64_BIT_POINTERS #define MYSTRUCT_64_BIT_MEMBERS MYSTRUCT_POINTERS #else if 64_BIT_FLOATS #define MYSTRUCT_64_BIT_MEMBERS MYSTRUCT_FLOATS #else #define MYSTRUCT_64_BIT_MEMBERS #endif // blah blah blah struct MyStruct { MYSTRUCT_64_BIT_MEMBERS MYSTRUCT_32_BIT_MEMBERS MYSTRUCT_16_BIT_MEMBERS MYSTRUCT_8_BIT_MEMBERS }; 

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

 $ cat foo.h struct foo { int x; char y; int b[5]; char c; }; $ pstruct foo.h struct foo { int foo.x 0 4 char foo.y 4 1 foo.b 8 20 char foo.c 28 1 } 

Большинство компиляторов C не будут делать этого, основываясь на том, что вы можете делать странные вещи (например, принимать адрес элемента в структуре, а затем использовать магию указателя для доступа к остальным, минуя компилятор). Известным примером являются двойные связанные списки в AmigaOS, которые использовали узлы-хранители в качестве главы и хвоста списка (это позволяет избежать ifs при перемещении списка). У головного узла-хранителя всегда будет pred == null а хвостовой узел будет иметь next == null , разработчики перевернули два узла в одну head_next null tail_pred структуру head_next null tail_pred . Используя адрес head_next или null как адрес головного и хвостового узлов, они сохранили четыре байта и одно распределение памяти (так как они нуждались в всей структуре только один раз).

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

Посмотрите на пакет #pragma. Это изменяет способ компиляции элементов в структуре. Вы можете использовать его, чтобы заставить их быть плотно упакованы вместе без пробелов.

Подробнее см. Здесь

Это будет зависеть от платформы / компилятора. Как отмечалось, большинство компиляторов разбивают все на 4-байтовое выравнивание (или хуже!), Поэтому предполагая структуру с двумя шортами и длинной:

 short long short 

будет занимать 12 байтов (с 2 * 2 байтами заполнения).

переупорядочить его

 short short long 

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

Что касается инструмента для изменения порядка, я просто (вручную) реорганизую структуру структуры, чтобы разные типы были объединены. Сначала поставьте все шорты, затем положите все длинные и т. Д. Если вы собираетесь сделать упаковку, это то, что инструмент будет делать в любом случае. У вас может быть 2 байта заполнения в середине в точках перехода между типами, но я бы не подумал об этом, о чем стоит беспокоиться.

Компилятор не может переопределять поля в структурах своей головой. Стандарт предусматривает, что поля должны быть отложены в том порядке, в котором они определены. Выполнение чего-то другого может сломать код тонким образом.

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

Думая о том, как я собираюсь сделать такой инструмент … Я думаю, что начну с информации отладки.

Получение размера каждой структуры из источника – боль. Он перекрывает многие работы, которые уже делает компилятор. Я недостаточно знаком с ELF, чтобы точно сказать, как извлечь информацию о размере структуры из двоичного файла debug, но я знаю, что информация существует, потому что отладчики могут ее отображать. Возможно, objdump или что-то еще в пакете binutils может получить это для вас тривиально (для платформ, которые используют ELF, по крайней мере).

После того, как у вас есть информация, остальное довольно просто. Заказывайте участников от самых маленьких до самых маленьких, пытаясь сохранить как можно больше порядок первоначальной структуры. С perl или python было бы легко перекреститься с остальной частью источника и, возможно, даже сохранить комментарии или #ifdefs в зависимости от того, насколько они были использованы. Самая большая боль изменила бы все инициализации структуры во всей кодовой базе. Хлоп.

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

У меня была такая же проблема. Как предложено в другом ответе, pstruct может помочь. Но он дает именно то, что нам нужно. Фактически pstruct использует отладочную информацию, предоставленную gcc. Я написал другой сценарий, основанный на той же идее.

Вы должны создавать сборные файлы с информацией об отладке -gstubs ( -gstubs ). (Можно было бы получить такую ​​же информацию от карлика, но я использовал тот же метод, что и pstruct). Хорошим способом сделать это без изменения процесса компиляции является добавление "-gstubs -save-temps=obj" к вашим параметрам компиляции.

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

  #!/usr/bin/perl -n if (/.stabs[\t ]*"([^:]*):T[()0-9,]*=s([0-9]*)(.*),128,0,0,0/) { my $struct_name = $1; my $struct_size = $2; my $desc = $3; # Remove unused information from input $desc =~ s/=ar\([0-9,]*\);[0-9]*;[-0-9]*;\([-0-9,]*\)//g; $desc =~ s/=[a-zA-Z_0-9]+://g; $desc =~ s/=[\*f]?\([0-9,]*\)//g; $desc =~ s/:\([0-9,]*\)*//g; my @members = split /;/, $desc; my ($prev_size, $prev_offset, $prev_name) = (0, 0, ""); for $i (@members) { my ($name, $offset, $size) = split /,/, $i; my $correct_offset = $prev_offset + $prev_size; if ($correct_offset < $offset) { my $diff = ($offset - $correct_offset) / 8; print "$struct_name.$name looks misplaced: $prev_offset + $prev_size = $correct_offset < $offset (diff = $diff bytes)\n"; } # Skip static members if ($offset != 0 || $size != 0) { ($prev_name, $prev_offset, $prev_size) = ($name, $offset, $size); } } } 

Хороший способ вызвать его:

 find . -name *.s | xargs ./detectPaddedStructs.pl | sort | un