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

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

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

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

РЕДАКТИРОВАТЬ: Быстрое уточнение - я хочу изменить порядок поля в исходном коде, чтобы избежать заполнения, а не "упаковывать" структуру, которая компилируется без заполнения.

РЕДАКТИРОВАТЬ № 2: Еще одно осложнение: в зависимости от конфигурации размеры некоторых типов данных также могут измениться. Очевидными являются указатели и различия указателей для разных архитектур, а также типы с плавающей запятой (16, 32 или 64-битные в зависимости от «точности»), контрольные суммы (8- или 16-битные в зависимости от «скорости») и некоторые прочие неочевидные вещи.

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

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


person Christoffer    schedule 15.05.2009    source источник
comment
Должны ли экземпляры этой структуры быть переносимыми между устройствами, или каждая архитектура может иметь свою собственную упаковку?   -  person Alnitak    schedule 15.05.2009
comment
Просто в стороне: я подумал, что это интересная проблема, и погуглил переупорядочение структуры Perl. Это был высший результат. Вопросу всего 15 минут!   -  person Paul Dixon    schedule 15.05.2009
comment
Alnitak: Да, на самом деле это код, который должен быть чрезвычайно переносимым :) Для каждой архитектуры нормально иметь свое собственное определение структуры, но нецелесообразно писать определения для конкретной архитектуры вручную.   -  person Christoffer    schedule 15.05.2009


Ответы (8)


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

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

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

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

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

#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
};
person Charlie    schedule 15.05.2009
comment
Пока кто-нибудь не построит более умного робота (для этой работы)! - person Autodidact; 15.05.2009
comment
Согласованный; здесь задействовано много контекстно-зависимых знаний. Конечно, если у вас очень большое количество структур, и вы можете внедрить все эти знания в формат, который может использовать инструмент, тогда это можно будет автоматизировать. - person Steve Melnikoff; 15.05.2009
comment
Спасибо за Ваш ответ. У меня вопрос об оптимальном порядке. В своем ответе вы упомянули, что оптимальный порядок сортируется от большего к меньшему. Есть ли какие-либо доказательства этого утверждения? Я пробовал много случаев, и все они не могут нарушить утверждение, поэтому мне интересно, как это доказать. Большое Вам спасибо. - person yoco; 02.01.2012
comment
Сортировка от большего к меньшему, как правило, не оптимальна. Общий случай — это проблема упаковки в корзину, которая является NP-сложной — есть некоторые интересные результаты, которые вы можете найти в Google, если вам интересно. Только в особом случае, когда размеры являются степенью двойки, он упаковывается идеально, не оставляя пробелов; довольно легко понять почему, взглянув только на этот случай. Каждый объект size-2^k заканчивается границей, выровненной по 2^k, которая также является границей, выровненной по 2^k-1, поэтому переход к меньшему размеру никогда не приводит к промежуткам. - person Charlie; 13.04.2012
comment
Сортировка по наибольшей степени двойного множителя по размеру, исходная позиция разрешения конфликтов — это действительно лучшее, что можно сделать для бинарных компьютеров (читай: почти всех в наши дни) без специальных знаний, как необходимое и оптимальное. выравнивания всегда являются степенями двойки и делителями размера. Конечно, если важна общая начальная последовательность или первый элемент, это требует особой осторожности. - person Deduplicator; 07.03.2020

Существует сценарий 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
}
person sigjuice    schedule 15.05.2009
comment
Хорошая идея, но похоже, что у pstruct есть проблемы с C++ :-( - person Jérôme Pouiller; 21.08.2013

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

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

person Aaron Digulla    schedule 15.05.2009
comment
Ни один компилятор C не сделает этого, так как это нарушит спецификацию, которая требует, чтобы поля структуры отображались в памяти в том порядке, в котором они объявлены в структуре. - person unwind; 15.05.2009
comment
Спецификации раскрывать не хотелось. - person Aaron Digulla; 15.05.2009
comment
@unwind по умолчанию это не делается, но в старых версиях gcc есть опция -fipa-struct-reorg для изменения порядка членов структуры stackoverflow.com/a/28780286/995714< /а> - person phuclv; 04.08.2015

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

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

person Howard May    schedule 15.05.2009
comment
Структуры не упакованы по умолчанию, потому что доступ к выровненным членам более эффективен. Изменение порядка структуры может уменьшить размер структуры, фактически не нарушая выравнивание любого члена. - person Artelius; 15.05.2009
comment
Не то, о чем он просил... хотя это даст ему оптимальную упаковку. - person Dan Olson; 15.05.2009

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

short
long
short

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

переупорядочив его, чтобы он был

short
short
long

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

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

person gbjbaanb    schedule 15.05.2009
comment
и подумать, что я удалил свой ответ о разных размерах типов данных! В любом случае, если вы объедините все поля одного типа, вы получите оптимальную упаковку независимо от размера каждого поля. - person gbjbaanb; 15.05.2009
comment
Я не уверен во всем, что касается 4-байтового выравнивания; компилятор гарантирует, что каждый элемент соответствует минимальным требованиям к выравниванию. Например, если для long double требуется выравнивание по 16 байтам, то char, за которым следует long double, оставит 15-байтовую дыру; но для типа short обычно требуется 2-байтовое выравнивание, а char, за которым следует short, оставляет 1-байтовую дыру (а ансамбль — char, short — с последующим long double оставляет 12-байтовую дыру, но за которой следует 32-битный int не оставит пробела между коротким и int). И Т. Д. - person Jonathan Leffler; 17.05.2009
comment
нет, char, за которым следует long-double, обычно занимает 1 байт + 3 байта + 16 байт. Выравнивание строки процессора работает таким образом, поэтому char можно извлечь без какого-либо смещения битов, но вы можете указать ему делать это по-другому, выровнять по 0 вместо 4, и ваше приложение просто будет выполняться медленнее. Вы думаете, что все должно быть выровнено по самому крупному индивидуальному типу. - person gbjbaanb; 17.05.2009

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

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

person Johan Kotlinski    schedule 15.05.2009

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

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

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

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

person Dan Olson    schedule 15.05.2009

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

Вы должны сгенерировать файлы сборки с отладочной информацией STUBS (-gstubs). (Можно было бы получить ту же информацию от dwarf, но я использовал тот же метод, что и 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
person Jérôme Pouiller    schedule 21.08.2013