LINUX.ORG.RU

C, препроцессор и размер/выравнивание типов

 ,


0

4

Привет, ЛОР!

Добавляю поддержку кросс-компиляции в один проект, возникла такая проблема: нужно сделать сишный файлик с размером и выравниванием некоторых типов без запуска кода. Что я хочу получить:

#define SIZEOF_typename 4
#define ALIGNMENT_OF_typename 4
...

Запускать код при этом нельзя, потому что кросскомпиляция, смерть, жопа и сотона. Сейчас там это делается через автоконф (AC_CHECK_SIZEOF/ALIGNOF) и это не работает именно по этой причине. Что делать?

Про #define ALIGN_OF_X alignof(x) я в курсе, но проект древний и в нём есть вычисления через препроцессор, которые я пока не хочу трогать.

#define SIZEOF_typename 4

Буду оригинален:

#define SIZEOF_typename (sizeof(typename))

#define ALIGNMENT_OF_typename 4

ВНЕЗАПНО

#define ALIGN_OF_X (alignof(x))

в нём есть вычисления через препроцессор

Вычисления вычислениями рознь. Если "вычисления" обычные Си-выражения - то решение выше. Если там дичь уровня

#if VAR == SOME_DEFINE
...
#elif OTHERVAR >= SOME_OTHER_DEFINE
...
#endif

то только руками под каждую из платформ.

LamerOk ★★★★★
()

Вместо alignof(x) в компиляторах, которые его нативно не поддерживают, можно сделать такое:

typedef struct { char a; x b; } _alignstruct_X;
#define ALIGN_OF_X ((size_t)&(((_alignstruct_X*)0)->b))

Разумеется, препроцессорные вычисления с этим работать не будут, но тут без шансов: все типы считаются компилятором, который логически (да, технически уже не так) запускается на уже препроцессированном исходнике, отправить из него данные «в прошлое» нельзя.

firkax ★★★★★
()

А, я что-то упустил из виду: код запускать нельзя, хорошо, но компилятор то можно запустить ещё раз?

Пишем такую штуку (отдельный исходник) align_test.c:

typedef struct { char a; TYPE b; } _alignstruct_X;
#define ALIGN_OF_X ((unsigned long)&(((_alignstruct_X*)0)->b))
int a[(ALIGN_OF_X==TEST)?1:-1];
int main(void) { return 0; }
И скрипт align_test.sh:
#!/bin/sh -e

TYPE="$1"
AMACRO="$2"
TEST=1

while ! gcc "-DTYPE=$TYPE" -DTEST=$TEST align_test.c -c -o /dev/null >> /dev/null 2>&1; do
  TEST=`expr $TEST + 1`
  if [ $TEST -gt 1000 ]; then
    echo "something went wrong, can't check alignment of type \"$TYPE\"" 1>&2
    exit 1
  fi
done

echo "#define ALIGN_OF_$AMACRO $TEST"

Запускаешь:

./align_test.sh "unsigned long" ULONG >> config.h

Ну и для sizeof аналогично.

Учти, что для ненативных типов вроде size_t надо инклюдить ещё какой-то хедер в align_test.c, иначе он молча повисит несколько минут и напишет что ничего не вышло. Чтобы заранее выяснить, доступен ли такой тип при данных хедерах - можно поптыаться скомпилировать предварительно ещё один исходник где этим типов просто переменная будет объявлена.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от firkax

но компилятор то можно запустить ещё раз?

Да, без проблем.

И скрипт align_test.sh:

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

hateyoufeel ★★★★★
() автор топика
Ответ на: комментарий от hateyoufeel

Убери >> /dev/null 2>&1 от вывода gcc и увидишь в чём дело.

Кстати переменную наверно лучше засунуть внутрь main() а то у меня gcc на неё какой-то непонятный варнинг пишет (внутри main Не пишет), вдруг сломается через ещё 15 версий.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от firkax
➜ C  cat align_test.c
typedef struct { char a; TYPE b; } _alignstruct_X;
#define ALIGN_OF_X ((unsigned long)&(((_alignstruct_X*)0)->b))
int a[(ALIGN_OF_X==TEST)?1:-1];
int main(void) { return 0; }
➜ C  gcc "-DTYPE=int" -DTEST=1 align_test.c -c -o /dev/null
align_test.c:3:5: error: size of array ‘a’ is negative
    3 | int a[(ALIGN_OF_X==TEST)?1:-1];
      |   
hateyoufeel ★★★★★
() автор топика
Ответ на: комментарий от firkax

Ну и? int a[-1] это ошибка компиляции, он падает и цикл скрипта идёт дальше перебирать варианты.

А… пожжи, то есть оно перебирает все варианты пока не найдёт нужный? Понял. Спасибо.

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

hateyoufeel ★★★★★
() автор топика
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от hateyoufeel

А… пожжи, то есть оно перебирает все варианты пока не найдёт нужный? Понял. Спасибо.

И автолулзы и симейк таким образом пытаются понять возможности платформы.

Если нет желания на это закладываться, то только руками.

LamerOk ★★★★★
()

Ну по идее есть *_MAX всякие для всех стандартных типов, а в c23 есть ещё и *_WIDTH.

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

Ну либо просто портянку макросов под все нужные типы с проверками границ.

Ну и static_assert не забыть, что полученное совпадает с sizeof/alignof.

Ivan_qrt ★★★★★
()
Ответ на: комментарий от hateyoufeel

А со структурами это будет работать?

Будет.

И как тут выравнивание получить? Оно меня куда больше интересует.

Уже предложенными выше способами. Любая магия препроцессора, а весь генерируемый код – один байт в секции «.data». Вне gcc+binutils не проверял. Поэкспериментируй.

opcode
()
Ответ на: комментарий от hateyoufeel
#include <stdalign.h>
char i = alignof(long);
> riscv64-xxx-gcc -c -o test.o test.c
> riscv64-xxx-objcopy -O binary test.o test.bin
> od -An -d test.bin
8
#include <stddef.h>
struct s {
        char x;
        int y;
};
char i = offsetof(struct s, y);
> riscv64-xxx-gcc -c -o test.o test.c
> riscv64-xxx-objcopy -O binary test.o test.bin
> od -An -d test.bin
4
opcode
()

Запускать код при этом нельзя, потому что кросскомпиляция, смерть, жопа и сотона

Мы при сборке ядра через qemu depmod запускали, думаю, что для простеньких конфигурационных тестов это тоже вариант

annulen ★★★★★
()

Вспомнил, что в разных версиях gcc/binutils менялись умолчания в какие секции отправлять символы и аттрибуты самих секций. Вдруг когда-нибудь там появится набивка или ещё что-то пойдёт не так. Ещё способ (bonus: однострочник).

> echo "char i = sizeof(int);" | \
> gcc -x c -S -o- - | \
> grep byte | \
> cut -f 3

4
opcode
()
Ответ на: комментарий от annulen

Мы при сборке ядра через qemu depmod запускали, думаю, что для простеньких конфигурационных тестов это тоже вариант

Туда нет qemu. Это всё ещё x86_64/arm64, просто ОС куда более нишевая.

hateyoufeel ★★★★★
() автор топика
Ответ на: комментарий от hateyoufeel

А можешь плз объяснить эту особую магию?

Пишешь код:

switch(sizeof(typename)) {
  case 2:
    break;
  case 4:
    break;
  case 8:
    break;
  case TEST_SIZE:
    break;
}
и компилишь тесты с перебором -DTEST_SIZE=4 и определяешь на какой строке ругнулось.

vodz ★★★★★
()
6 апреля 2025 г.

Я разобрался, почему AC_COMPUTE_INT, который делает тест в духе того что @firkax написал, сыпался.

У меня тут слегка кастомная libc и там offsetof() был задан макросом #define offsetof(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT)).

GCC почему-то считает, что результат этого не является константным выражением и генерит код с VLA, который сыпется и не может. При замене макроса на #define offsetof(TYPE, ELEM) __builtin_offsetof(TYPE, ELEM) всё магически заработало.

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

hateyoufeel ★★★★★
() автор топика
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от firkax

Массив с выражением с offsetof в качестве размера компилятором считался как VLA. То есть GCC не осилил распознать результат того макроса за константу.

hateyoufeel ★★★★★
() автор топика
Последнее исправление: hateyoufeel (всего исправлений: 2)
Ответ на: комментарий от hateyoufeel

GCC почему-то считает, что результат этого не является константным выражением и генерит код с VLA,

У тебя либо под целевую платформу gcc рабоает через пень колоду, либо структура описана через жопу. Вот как выглядит реализация этого макроса обёрнутая в функцию под amd64:

.LFB0:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	$8, %eax
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

либо под целевую платформу gcc рабоает через пень колоду,

Там x86_64, просто остальная часть таргета всратая.

либо структура описана через жопу

Код из теста автолулзов, очищенный от мусора:

#include <stdio.h>

int
main ()
{
  static int test_array [1 - 2 * !((offsetof(struct { char c; char ty; },ty)) >= 0)];
  test_array [0] = 0;
  return test_array [0];
}

Это не мой код. Это автолулзы такое говно для тестов генерят.

hateyoufeel ★★★★★
() автор топика
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от hateyoufeel

Это я понял, а как выглядел этот VLA в результате то?

УМВР

https://godbolt.org/z/YWdvq98on

#include <stdio.h>

#define offsetof(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT))

struct st { int x,a; };
int main(void) {
    int v[(offsetof(struct st, a)==4)?-1:0];
    printf("%u", (unsigned)offsetof(struct st, a));
    printf("%u", (unsigned)sizeof(v));
    return 0;
}
firkax ★★★★★
()
Ответ на: комментарий от hateyoufeel

Код из теста автолулзов, очищенный от мусора:

Успешно компилируется в валидный код:

main:
.LFB1:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	$0, test_array.0(%rip)
	movl	test_array.0(%rip), %eax
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
LamerOk ★★★★★
()
Ответ на: комментарий от firkax

Это я понял, а как выглядел этот VLA в результате то?

А.. понятия не имею. Я просто словил ошибку компилятора, указывающую на VLA, в какой-то момент. Собрал с -Werror=vla и потом поправил макрос на __builtin_offsetof. Дальше я копать это всё даже близко не хочу. В тред отписался скорее для будущих поколений, вдруг кто ещё в такое говно вляпается.

hateyoufeel ★★★★★
() автор топика
Ответ на: комментарий от LamerOk

Ты лучше отпишись, под какую платформу ты скачешь по всем этим граблям

С точки зрения GCC, это всё идёт с -ffreestanding.

потому как по описанию ты чуть ли не первопроходец

Ну, не совсем. Но штука крайне нишевая.

hateyoufeel ★★★★★
() автор топика
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от hateyoufeel

Хм,

https://godbolt.org/z/zoYGrzGn7

#include <stdio.h>

int main(void) {
    static int v[(size_t)&(*(int*)0)];
    return 0;
}

И правда ругается даже на такое. А не ругался в моём примере выше из-за того что там переменная не static - то есть ругань на static vla который запрещён.

firkax ★★★★★
()
Ответ на: комментарий от firkax

он не на ноль ругается.

Потому что расширениями компилятора для тонн юниксового struct { ..., char[0]} нулевые массивы допустимы, но трактуются как VLA. Ну а static VLA - это такое себе. Шланг, к слову, всё выводит и комплирует, но запись в жалобную книгу делает.

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

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

Я пока виню во всём автолулзы. Их код для тестов просто чудовищен зачастую.

GCC чтоле тоже какой-то защищённый.

Нет, вполне обычный из дистра.

hateyoufeel ★★★★★
() автор топика
Ответ на: комментарий от hateyoufeel

Их код для тестов просто чудовищен зачастую.

Ну напши лучше, то что он делает:

Macro: AC_COMPUTE_INT (var, expression, [includes = ‘AC_INCLUDES_DEFAULT’], [action-if-fails])
Store into the shell variable var the value of the integer expression. The value should fit in an initializer in a C variable of type signed long. To support cross compilation (in which case, the macro only works on hosts that use twos-complement arithmetic), it should be possible to evaluate the expression at compile-time.

Давай посмотрим, насколько ты сделаешь лучше. ;)

Я пока виню во всём автолулзы.

А надо бы того, кто под x86_64 переизобретает stdint / stddef. Это какая-то особая форма спецолимпиады.

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

Давай посмотрим, насколько ты сделаешь лучше. ;)

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

hateyoufeel ★★★★★
() автор топика
Ответ на: комментарий от hateyoufeel

сам факт того, что из компилятора приходится выковыривать информацию о выравнивании вот таким вот чудовищным способом,

У тебя есть другой способ выковыривать информацию о выравнивании с помощью языка программирования, где нет концепции выравнивания? На 99.9(9)% других языков ты вообще не сможешь этого сделать.

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

У тебя есть другой способ выковыривать информацию о выравнивании с помощью языка программирования, где нет концепции выравнивания?

В Си есть концепт выравнивания.

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

hateyoufeel ★★★★★
() автор топика
Ответ на: комментарий от hateyoufeel

В Си есть концепт выравнивания

Нет, в Си как языке программирования нет концепции выравнивания. Это деталь имплементации, важная для реализации компилятора / целевой платформы.

Если ты сам лично не начнёшь шалить ручками с объектами в памяти приведением указателей, ты можешь прожить всю жизнь, написав тонны кода на си, и никогда ничего про выравнивание не знать. Как собственно и обстоят дела у 99% сишников.

LamerOk ★★★★★
()
Ответ на: комментарий от hateyoufeel

У меня тут слегка кастомная libc и там offsetof() был задан

🤦‍♂️

GCC почему-то считает, что результат этого не является константным выражением

А почему он должен считать это константным выражением?

При замене макроса

🤦‍♂️

Нормальные люди не маются дурью и берут offsetof из stddef.h

utf8nowhere ★★★
()
Последнее исправление: utf8nowhere (всего исправлений: 1)
Ответ на: комментарий от LamerOk

Нет, в Си как языке программирования нет концепции выравнивания. Это деталь имплементации, важная для реализации компилятора / целевой платформы.

https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2310.pdf

Пункт 6.2.8, «Alignment of objects». Читаем, просвещаемся.

Как собственно и обстоят дела у 99% сишников.

У 99% сишников дела обстоят откровенно говоря херово, они не умеют писать на своём языке и делают полное говно.

hateyoufeel ★★★★★
() автор топика
Ответ на: комментарий от utf8nowhere

А почему он должен считать это константным выражением?

Потому что это константное выражение. Например, его можно записать в constexpr.

Нормальные люди не маются дурью и берут offsetof из stddef.h

Кастомный таргет со своей libc. stddef.h тут пишем тоже мы. Был вариант попробовать портировать musl, но там смерть-жопа-сотона. Вообще внезапно переносимых libc под freestanding target кот наплакал.

hateyoufeel ★★★★★
() автор топика
Последнее исправление: hateyoufeel (всего исправлений: 1)