Используйте выделенный раздел ELF для "сбора" структур данных.
Например, определите свою структуру данных в info.h как
#ifndef INFO_H
#define INFO_H
#ifndef INFO_ALIGNMENT
#if defined(__LP64__)
#define INFO_ALIGNMENT 16
#else
#define INFO_ALIGNMENT 8
#endif
#endif
struct info {
long key;
long val;
} __attribute__((__aligned__(INFO_ALIGNMENT)));
#define INFO_NAME(counter) INFO_CAT(info_, counter)
#define INFO_CAT(a, b) INFO_DUMMY() a ## b
#define INFO_DUMMY()
#define DEFINE_INFO(data...) \
static struct info INFO_NAME(__COUNTER__) \
__attribute__((__used__, __section__("info"))) \
= { data }
#endif /* INFO_H */
.Макрос INFO_ALIGNMENT
- это выравнивание, используемое компоновщиком для размещения каждого символа отдельно в секции info
.Важно, чтобы компилятор C согласился, поскольку в противном случае содержимое раздела не может рассматриваться как массив.(Вы получите неправильное количество структур, и только первая (плюс каждый N-й) будет правильной, остальные структуры искажены. По сути, компилятор C и компоновщик не согласились с размером каждой структуры враздел «массив».)
Обратите внимание, что вы можете добавить макросы препроцессора для точной настройки INFO_ALIGNMENT
для каждой используемой вами архитектуры, но вы также можете переопределить ее, например, в вашем Makefile при компиляциивремя.(Для GCC укажите -DINFO_ALIGNMENT=32
, например.)
Атрибут used
гарантирует, что определение передается в объектном файле, даже если на него не ссылаются иначе в том же файле данных.Атрибут section("info")
помещает данные в специальный раздел info
в объектном файле.Имя раздела (info
) зависит от вас.
Это критические части, в противном случае вам решать, как определить макрос или определить его вообще.Использовать макрос легко, потому что не нужно беспокоиться об использовании уникального имени переменной для структуры.Кроме того, если указан хотя бы один элемент, все остальные будут инициализированы нулем.
В исходных файлах вы определяете объекты данных как, например,
#include "info.h"
/* Suggested, easy way */
DEFINE_INFO(.key = 5, .val = 42);
/* Alternative way, without relying on any macros */
static struct info foo __attribute__((__used__, __section__("info"))) = {
.key = 2,
.val = 1
};
Компоновщик предоставляет символы __start_info
и __stop_info
, чтобы получить структуры в разделе info
.В вашем main.c используйте, например,
#include "info.h"
extern struct info __start_info[];
extern struct info __stop_info[];
#define NUM_INFO ((size_t)(__stop_info - __start_info))
#define INFO(i) ((__start_info) + (i))
, чтобы вы могли перечислить все информационные структуры.Например,
int main(void)
{
size_t i;
printf("There are %zu info structures:\n", NUM_INFO);
for (i = 0; i < NUM_INFO; i++)
printf(" %zu. key=%ld, val=%ld\n", i,
__start_info[i].key, INFO(i)->val);
return EXIT_SUCCESS;
}
Для иллюстрации я использовал оба доступа к массиву __start_info[]
(вы можете, конечно, #define SOMENAME __start_info
, если хотите, просто убедитесь, что вы не используете SOMENAME
где-либо еще в main.c, так что вместо этого вы можете использовать SOMENAME[]
в качестве массива), а также макрос INFO()
.
Давайте рассмотрим практический пример калькулятора RPN.
Мыиспользуйте секцию ops
для определения операций, используя средства, определенные в ops.h :
#ifndef OPS_H
#define OPS_H
#include <stdlib.h>
#include <errno.h>
#ifndef ALIGN_SECTION
#if defined(__LP64__) || defined(_LP64)
#define ALIGN_SECTION __attribute__((__aligned__(16)))
#elif defined(__ILP32__) || defined(_ILP32)
#define ALIGN_SECTION __attribute__((__aligned__(8)))
#else
#define ALIGN_SECTION
#endif
#endif
typedef struct {
size_t maxsize; /* Number of values allocated for */
size_t size; /* Number of values in stack */
double *value; /* Values, oldest first */
} stack;
#define STACK_INITIALIZER { 0, 0, NULL }
struct op {
const char *name; /* Operation name */
const char *desc; /* Description */
int (*func)(stack *); /* Implementation */
} ALIGN_SECTION;
#define OPS_NAME(counter) OPS_CAT(op_, counter, _struct)
#define OPS_CAT(a, b, c) OPS_DUMMY() a ## b ## c
#define OPS_DUMMY()
#define DEFINE_OP(name, func, desc) \
static struct op OPS_NAME(__COUNTER__) \
__attribute__((__used__, __section__("ops"))) = { name, desc, func }
static inline int stack_has(stack *st, const size_t num)
{
if (!st)
return EINVAL;
if (st->size < num)
return ENOENT;
return 0;
}
static inline int stack_pop(stack *st, double *to)
{
if (!st)
return EINVAL;
if (st->size < 1)
return ENOENT;
st->size--;
if (to)
*to = st->value[st->size];
return 0;
}
static inline int stack_push(stack *st, double val)
{
if (!st)
return EINVAL;
if (st->size >= st->maxsize) {
const size_t maxsize = (st->size | 127) + 129;
double *value;
value = realloc(st->value, maxsize * sizeof (double));
if (!value)
return ENOMEM;
st->maxsize = maxsize;
st->value = value;
}
st->value[st->size++] = val;
return 0;
}
#endif /* OPS_H */
Основной набор операций определен в ops-basic.c :
#include "ops.h"
static int do_neg(stack *st)
{
double temp;
int retval;
retval = stack_pop(st, &temp);
if (retval)
return retval;
return stack_push(st, -temp);
}
static int do_add(stack *st)
{
int retval;
retval = stack_has(st, 2);
if (retval)
return retval;
st->value[st->size - 2] = st->value[st->size - 1] + st->value[st->size - 2];
st->size--;
return 0;
}
static int do_sub(stack *st)
{
int retval;
retval = stack_has(st, 2);
if (retval)
return retval;
st->value[st->size - 2] = st->value[st->size - 1] - st->value[st->size - 2];
st->size--;
return 0;
}
static int do_mul(stack *st)
{
int retval;
retval = stack_has(st, 2);
if (retval)
return retval;
st->value[st->size - 2] = st->value[st->size - 1] * st->value[st->size - 2];
st->size--;
return 0;
}
static int do_div(stack *st)
{
int retval;
retval = stack_has(st, 2);
if (retval)
return retval;
st->value[st->size - 2] = st->value[st->size - 1] / st->value[st->size - 2];
st->size--;
return 0;
}
DEFINE_OP("neg", do_neg, "Negate current operand");
DEFINE_OP("add", do_add, "Add current and previous operands");
DEFINE_OP("sub", do_sub, "Subtract previous operand from current one");
DEFINE_OP("mul", do_mul, "Multiply previous and current operands");
DEFINE_OP("div", do_div, "Divide current operand by the previous operand");
Калькулятор ожидает, что каждое значение и операнд будут отдельным аргументом командной строки для простоты.Наш main.c содержит поиск операций, базовое использование, анализ значений и печать результата (или ошибки):
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "ops.h"
extern struct op __start_ops[];
extern struct op __stop_ops[];
#define NUM_OPS ((size_t)(__stop_ops - __start_ops))
static int do_op(stack *st, const char *opname)
{
struct op *curr_op;
if (!st || !opname)
return EINVAL;
for (curr_op = __start_ops; curr_op < __stop_ops; curr_op++)
if (!strcmp(opname, curr_op->name))
break;
if (curr_op >= __stop_ops)
return ENOTSUP;
return curr_op->func(st);
}
static int usage(const char *argv0)
{
struct op *curr_op;
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv0);
fprintf(stderr, " %s RPN-EXPRESSION\n", argv0);
fprintf(stderr, "\n");
fprintf(stderr, "Where RPN-EXPRESSION is an expression using reverse\n");
fprintf(stderr, "Polish notation, and each argument is a separate value\n");
fprintf(stderr, "or operator. The following operators are supported:\n");
for (curr_op = __start_ops; curr_op < __stop_ops; curr_op++)
fprintf(stderr, "\t%-14s %s\n", curr_op->name, curr_op->desc);
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}
int main(int argc, char *argv[])
{
stack all = STACK_INITIALIZER;
double val;
size_t i;
int arg, err;
char dummy;
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
return usage(argv[0]);
for (arg = 1; arg < argc; arg++)
if (sscanf(argv[arg], " %lf %c", &val, &dummy) == 1) {
err = stack_push(&all, val);
if (err) {
fprintf(stderr, "Cannot push %s to stack: %s.\n", argv[arg], strerror(err));
return EXIT_FAILURE;
}
} else {
err = do_op(&all, argv[arg]);
if (err == ENOTSUP) {
fprintf(stderr, "%s: Operation not supported.\n", argv[arg]);
return EXIT_FAILURE;
} else
if (err) {
fprintf(stderr, "%s: Cannot perform operation: %s.\n", argv[arg], strerror(err));
return EXIT_FAILURE;
}
}
if (all.size < 1) {
fprintf(stderr, "No result.\n");
return EXIT_FAILURE;
} else
if (all.size > 1) {
fprintf(stderr, "Multiple results:\n");
for (i = 0; i < all.size; i++)
fprintf(stderr, " %.9f\n", all.value[i]);
return EXIT_FAILURE;
}
printf("%.9f\n", all.value[0]);
return EXIT_SUCCESS;
}
Обратите внимание, что, если было много операций, построение хэш-таблицычтобы ускорить поиск операций, было бы много смысла.
Наконец, нам нужен Makefile , чтобы связать все это вместе:
CC := gcc
CFLAGS := -Wall -O2 -std=c99
LDFLAGS := -lm
OPS := $(wildcard ops-*.c)
OPSOBJS := $(OPS:%.c=%.o)
PROGS := rpncalc
.PHONY: all clean
all: clean $(PROGS)
clean:
rm -f *.o $(PROGS)
%.o: %.c
$(CC) $(CFLAGS) -c $^
rpncalc: main.o $(OPSOBJS)
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
Поскольку этот форум делаетне сохраняйте Tab s, и make требует их для отступа, вам, вероятно, нужно исправить отступ после вставки копии выше.Я использую sed -e 's|^ *|\t|' -i Makefile
Если вы скомпилируете (make clean all
) и запустите (./rpncalc
) выше, вы увидите информацию об использовании:
Usage: ./rpncalc [ -h | --help ]
./rpncalc RPN-EXPRESSION
Where RPN-EXPRESSION is an expression using reverse
Polish notation, and each argument is a separate value
or operator. The following operators are supported:
div Divide current operand by the previous operand
mul Multiply previous and current operands
sub Subtract previous operand from current one
add Add current and previous operands
neg Negate current operand
и если вы запуститенапример, ./rpncalc 3.0 4.0 5.0 sub mul neg
, вы получите результат 3.000000000
.
Теперь давайте добавим несколько новых операций, ops-sqrt.c :
#include <math.h>
#include "ops.h"
static int do_sqrt(stack *st)
{
double temp;
int retval;
retval = stack_pop(st, &temp);
if (retval)
return retval;
return stack_push(st, sqrt(temp));
}
DEFINE_OP("sqrt", do_sqrt, "Take the square root of the current operand");
ПосколькуMakefile выше компилирует все исходные файлы C, начиная с ops-
, в конечный двоичный файл, единственное, что вам нужно сделать, это перекомпилировать исходный код: make clean all
.При выполнении ./rpncalc
теперь выводится
Usage: ./rpncalc [ -h | --help ]
./rpncalc RPN-EXPRESSION
Where RPN-EXPRESSION is an expression using reverse
Polish notation, and each argument is a separate value
or operator. The following operators are supported:
sqrt Take the square root of the current operand
div Divide current operand by the previous operand
mul Multiply previous and current operands
sub Subtract previous operand from current one
add Add current and previous operands
neg Negate current operand
, и у вас есть новый оператор sqrt
.
Тестирование, например, ./rpncalc 1 1 1 1 add add add sqrt
, дает 2.000000000
, как и ожидалось.