Можно ли разместить структуры в стеке с его определением, скрытым в исходном файле? - PullRequest
1 голос
/ 12 марта 2019

У меня есть следующий заголовочный файл:

struct StackList_s;
typedef struct StackList_s StackList_t;
// From here I add in the method signatures

И следующий исходный файл:

struct StackList_s
{
    integer_t count;
    struct StackListNode_s *top; // Here begins the linked list
    // Some other members that store information about the stack
    integer_t version_id;
};
// From here I define StackListNode_s and implement the StackList_s functions
// Note that the user will never manipulate directly a StackListNode_s
// There are functions that will handle the free() of each node correctly

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

В настоящее время для получения нового стека необходимо использовать следующее:

// malloc(sizeof(StackList_t)) and set members to default
StackList_t *stack = stl_new(/* Some info parameters */);

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

StackList_t stack;
stl_init(&stack, /* Info parameters */); // No malloc, only setting members to 0

Но, конечно, я не могу этого сделать, потому чтоопределение struct StackList_s находится в исходном файле.Итак, вот мои вопросы:

  • Можно ли одновременно не разрешить доступ к элементам структуры и выделить эту же структуру в стеке?
  • Есть лилюбой способ сообщить компилятору размер моей структуры?

Ответы [ 3 ]

2 голосов
/ 12 марта 2019

Вы можете сделать это с VLA или alloca в Linux:

Заголовок библиотеки:

struct StackList_s;
typedef struct StackList_s StackList_t;
extern const size_t StackList_size;

// If you're using VLAs
extern const size_t StackList_align;
StackList_t* stl_init_inline(char stack_source[], ...);

Источник библиотеки:

#include "header.h"

struct StackList_s {
    // ...
};

const size_t StackList_size = sizeof(StackList_t);

// If you're using VLAs
#include <stdalign.h>
#include <stdint.h>

const size_t StackList_align = alignof(StackList_t);
StackList_t* stl_init_inline(char stack_source[], ...) {
    // align the address to the nearest multiple of StackList_align
    uintptr_t address = (uintptr_t) ((void*) stack_source);
    if (address % StackList_align != 0) {
        address += StackList_align - address % StackList_align;
    }
    StackList_t* stack = (StackList_t*) ((void*) address);
    stl_init(stack, ...);
    return stack;
}

Основной источник

#include <header.h>

StackList_t* stack = alloca(Stacklist_size);
stl_init(stack, ...);

char stack_source[StackList_size + StackList_align - 1];  // Not compile time.
StackList_t* stack = stl_init_inline(stack_source, ...);

Этот будет размещать его в стеке, и вам не нужно будет его освобождать, но он медленнее и многословнее, чемпросто StackList_t stack_source;.(И alloca только для Linux)

Для второго вопроса вам нужно полное определение структуры, чтобы получить ее размер.К распространенным ошибкам относится тот факт, что sizeof(struct { int a; }) == sizeof(struct { int a; }) может быть ложным.Хотя, вероятно, этого не произойдет, поэтому вы можете сделать #define StackList_size sizeof(struct { integer_t count; struct StackListNode_s *top; integer_t version_id; }), но это также приводит к значительному дублированию кода.

Лично я бы просто поместил определение структуры в заголовочный файл и просто объявил "don"не связывайтесь с участниками, или мои методы не сработают "в комментарии где-то (возможно, имена начинаются с _, чтобы намекнуть, что они закрытые)

1 голос
/ 13 марта 2019

Некоторые реализации гарантируют, что вызовы между блоками компиляции будут обрабатываться способом, совместимым с двоичным интерфейсом приложений (ABI) платформы, независимо от того, что будет вызывать вызываемая функция с хранилищем, чей адрес она получает, или каким вызывающим объектомбудет делать с хранилищем, адрес которого он предоставляет, или будет делать с таким хранилищем, как только функция вернется.В таких реализациях дано что-то вроде:

// In header
typedef union FOO_PUBLIC_UNION { 
  uint64_t dat[4]; // Allocate space
  double dummy_align1; // Force alignment
  void *dummy_align2; // Force alignment
} FOO;
void act_on_foo(FOO_PUBLIC_UNION*);

// In code
FOO x = {0};
act_on_foo(&x);

в одном модуле компиляции и что-то вроде:

struct FOO_PRIVATE {
  int this; float that; double whatever;
};

typedef union FOO_PUBLIC_UNION { uint64_t dat[4]; struct FOO_PRIVATE priv; } FOOPP;

void act_on_foo(FOO *p)
{
  FOOPP *pp = (FOOPP*)p;
  pp->priv.whatever = 1234.567;
}

при условии, что размеры FOO и FOOPP совпадают,Поведение вызова внешней функции из первого модуля компиляции будет определяться как распределение sizeof(FOO) байтов, обнуление их и передача их адреса в act_on_foo, чье поведение затем будет определяться как воздействие на байты, к которым она получает адресбезотносительно к тому, как они получили свои значения или что вызывающий будет делать с ними позже.

К сожалению, даже при том, что почти каждая реализация должна быть способна производить поведение, совместимое с вызовом функции, о которой она ничего не знает, существуетнет стандартного способа указать компилятору, что конкретный вызов функции должен рассматриваться как «непрозрачный».Реализации, предназначенные для целей, где это было бы полезно, могли и, как правило, поддерживали такую ​​семантику с помощью «обычных» вызовов функций независимо от того, требовал ли Стандарт, и такая семантика давала бы небольшую ценность для реализаций, предназначенных только для целей, где они не были бы полезны,К сожалению, это привело к Уловке 22: у Стандарта нет оснований поручать вещи, которые реализации могут делать свободно, с мандатом или без него, в тех случаях, когда они полезны, но некоторые авторы компиляторов относятся к отсутствию Стандарта.мандат в качестве поощрения отказать в поддержке.

1 голос
/ 12 марта 2019

Вы можете сделать что-то похожее на ответ Артье, не используя VLA, используя вместо этого #define

Заголовок:

#define STACKLISTSIZE 32
typedef uint8_t stl_storage[STACKLISTSIZE];
typedef struct stacklist_s stacklist_t;

stacklist_t* stl_create_from_stack(stl_storage b);  //user provides memory
stacklist_t* stl_allocate(void);  //library allocates memory, user must free.

Источник:

int myfunction()
{
  stl_storage x;
  stacklist_t* sp = stl_create_from_stack(x);
  //do something with sp.
}

Убедитесь, чтоу вас есть время компиляции, что sizeof(stack_s) == STACKSTRUCTSIZE в файле реализации.

...