Есть ли у avr-libc причины для выравнивания переменных-членов класса С++ по словам?

c++

Это звучит достаточно просто, но я изо всех сил пытаюсь найти хороший ответ.

Предположим, я объявляю класс C++ с закрытыми членами, которые в совокупности занимают нечетное количество байтов. Скажи...

class Foo {
private:
    uint8_t bitflags;
    uint32_t millis;
}

или

class Foo {
private:
    uint8_t rawData[3];
}

Если я скомпилирую его с помощью GCC или Clang (возможно, в интегрированной среде разработки Arduino или в многократной оболочке внутри PlatformIO, CMake и такой среды разработки, как CLion), существует ли какой-либо вероятный сценарий, при котором компилятор (при ориентации на 8-разрядную AVR ... в частности, ATmega2560 на Arduino Mega2560) будет склонен добавлять байты заполнения к части класса, хранящейся в SRAM? Скажем... между bitflags и millis или между экземпляром Foo и uint64_t сразу после него?

Если да, то что мне нужно использовать, чтобы сказать компилятору, "абсолютно, решительно НЕ заполнять переменные в SRAM (и, если вы окажетесь в ситуация, когда это кажется неизбежным, по крайней мере, вывести заметное предупреждение во время компиляции, если не прямую ошибку остановки компиляции)?

Тогда... если я это сделаю... будут ли какие-либо негативные последствия, если я сделаю это с кодом, предназначенным (и в конечном счете работающим) на 8-битном AVR?

Насколько мне известно, 8-битный AVR никогда не имел специальных кодов операций для копирования пары последовательных байтов между регистрами и SRAM (я думаю, потому что Atmel буквально закончилось место для сопоставления кодов операций, и некуда было поместить гипотетические новые коды операций, такие как "ldw rz, $f00d"), а movw применялся только к парам регистры... так что, теоретически, компилятору не нужно оптимизировать "оптимизировать" Доступ к SRAM должен быть выровнен по словам.

Тем не менее... Я полагаю, не исключено, что компилятор может по умолчанию использовать выравнивание по словам (даже на тех платформах, где это не имеет значения), только для ради согласованности ABI между различными платформами.

Прежде чем кто-то предложит, "попробуйте и убедитесь!"... Я достаточно продвинулся в изучении C++, чтобы знать, что с C++ вы не можете доверять< /em> "компилируется без ошибок и проходит модульные тесты на моем компьютере с {этой} IDE" как универсальное доказательство правильности для C++, включающее "определяемую реализацией" или "не определено" поведение. Насколько я знаю, поведение заполнения "определяется реализацией".

, 👍0

Обсуждение

Относительно «поведения _padding определяется реализацией»_: Насколько я понимаю, оно определяется ABI: правила заполнения, используемые в скомпилированной библиотеке, должны соответствовать правилам вызывающего кода. Так что можете попробовать и посмотреть., @Edgar Bonet

Мне не показалось совершенно сумасшедшим, что они *могли* выбрать выравнивание слов в структурах, *потому что* они передаются в регистрах (если они достаточно малы) в соглашении о вызовах и потому что пары, используемые MOVW, всегда выравниваются в том смысле, что они четные: нечетные пары (которые также выравниваются в файле регистров с отображением памяти (когда он отображается). Тем не менее, не видно (из тестов), что они это сделали, вероятно, потому что это было Не стоит раздувать структуры, которые, возможно, никогда не будут оптимизированы с использованием MOVW. Я понятия не имею, какой ответ удовлетворит этот вопрос., @timemage

Хорошо, а как насчет того, чтобы я немного упростил это, и мы говорим о классе, единственным членом которого является uint8_t[3]. По сути, я хочу убедиться, что, если я буду из кожи вон лезть и сделаю свою собственную сложную битовую упаковку, чтобы все уместилось в 3 байта, компилятор не будет просто смеяться надо мной и заставлять его занимать 4 байта *все равно*., @Bitbang3r

Похоже, вы движетесь в направлении чего-то вроде расширения GCC __attribute__ ((__packed__)) или способа #pragma или атрибута C++ (*способ*; это все еще нестандартно) для его указания , но на самом деле я не знаю. Я ожидаю, что этот атрибут ничего не делает в avr-gcc, потому что в любом случае это нормальное поведение в этом компиляторе с общими параметрами. Но опять же... я не знаю., @timemage


1 ответ


Лучший ответ:

1

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

Кроме того, он имеет #pragma для определения выравнивания, которое можно использовать в качестве альтернативы.

Но, как вы уже подозреваете, для этой 8-битной цели не должно быть причин вставлять байты заполнения.


Итак, ваш вопрос сводится к поиску метода обнаружения нежелательных отступов. Вы можете сделать это стандартным совместимым способом, который я покажу ниже. Да, он вводит дополнительные типы, по одному на структуру. Но он не генерирует никаких ненужных объектов, которые съедают ваши драгоценные ресурсы.

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

Этот пример успешно компилируется с ПК в качестве цели, но не с AVR в качестве цели. Это намеренно для демонстрации работы, потому что с целью AVR тип UP не должен иметь байтов заполнения.

#define ASSERT_TYPE_SIZE(Type, size) \
    typedef char Assert##Type##Size[2 * (sizeof(Type) == (size)) - 1]

typedef struct {
    char c;
    long l;
} UP;
ASSERT_TYPE_SIZE(UP, sizeof (long) + sizeof(long));

typedef struct __attribute__((packed)) {
    char c;
    long l;
} P1;
ASSERT_TYPE_SIZE(P1, sizeof (char) + sizeof(long));

#pragma pack(1)
typedef struct {
    char c;
    long l;
} P2;
ASSERT_TYPE_SIZE(P2, sizeof (char) + sizeof(long));
#pragma pack()
,