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

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

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

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

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

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

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

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

Ответ 1

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

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

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

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

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

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

Ответ 2

Существует Perl script, называемый pstruct, который обычно включается в установки Perl. script сбрасывает смещения и размеры элементов структуры. Вы можете либо изменить 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
}

Ответ 3

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

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

Ответ 4

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

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

Ответ 5

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

short
long
short

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

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

short
short
long

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

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

Ответ 6

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

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

Ответ 7

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

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

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

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

Ответ 8

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

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

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

    #!/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