Как выставить размер структуры C, не раскрывая ее тип? - PullRequest
0 голосов
/ 20 февраля 2019

У меня есть следующее в C (не C ++!):

module.c
    struct Private {...};
    void foo(void* private, int param) {...}

module.h
    #define PRIVATE_SIZE ???;
    void foo(void* private, int param);

main.c
    char m1[PRIVATE_SIZE];
    char m2[PRIVATE_SIZE];

    int main()
    {
        foo(m1, 10);
        foo(m2, 20);
    }

Как я могу выставить sizeof (Private) во время компиляции, чтобы приложение могло статически выделить хранилище без предоставления закрытого типа?

Обратите внимание, что это очень ограниченная встроенная система, и динамическое распределение недоступно.

Ответы [ 5 ]

0 голосов
/ 20 февраля 2019

C99 позволяет использовать массив переменной длины.

private.h:

#include <stdio.h>
extern const size_t size;

private.c:

#include "private.h"
struct Private {
        int x;
        int y;
        int z;
};

const size_t size = sizeof(struct Private);

main.c:

#include <stdio.h>
#include "private.h"
int main(void) {
        char m1[size]; //variable length array
        printf("Size of m1 = %ld\n", sizeof(m1));
}
0 голосов
/ 20 февраля 2019

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

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

Например:

module.h:

#include <inttypes.h>

struct Public {
    uint64_t opaque1;
    uint64_t opaque2;
    uint64_t opaque3;
};

void init(struct Public *p);

module.c:

#include <assert.h>
#include <stdalign.h>
#include "module.h"

struct Private {
    int a;
    double b;
    float c;
};

static_assert(sizeof(struct Private)==sizeof(struct Public), "sizes differ");
static_assert(alignof(struct Private)==alignof(struct Public), "alignments differ");

void init(struct Public *p)
{
    struct Private *pr = (struct Private *)p;
    pr->a = 2;
    pr->b = 2.5;
    pr->c = 2.4f;
}

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


Другой, более надежный вариант, если у вас есть представление о максимальном количестве объектов, которые вы хотите поддерживать.Если это так, вы можете иметь статический массив этих объектов в вашем файле реализации, и функция init будет возвращать указатель на один из объектов в этом списке.Тогда у вас будет связанная функция очистки, которая освободит экземпляр.

Например:

module.c:

struct Private {
    int a;
    double b;
    float c;
};

struct PrivateAllocator {
    struct Private obj;
    int used;
};

struct PrivateAllocator list[5] = {
    { { 0, 0, 0}, 0 },
    { { 0, 0, 0}, 0 },
    { { 0, 0, 0}, 0 },
    { { 0, 0, 0}, 0 },
    { { 0, 0, 0}, 0 }
};

struct Private *private_init()
{
    int i;
    for (i=0; i<5; i++) {
        if (!list[i].used) {
            list[i].used = 1;
            return &list[i].obj;
        }
    }
    return NULL;
}

void private_free(struct Private *p)
{
    int i;
    for (i=0; i<5; i++) {
        if (&list[i].obj == p) {
            list[i].used = 0;
            return;
        }
    }
}
0 голосов
/ 20 февраля 2019

В соответствующем коде C вы не можете создать статический экземпляр произвольного неизвестного типа, даже если вы знаете его размер во время компиляции (даже если вы не знаете выравнивание).

Допустим, вы пытаетесь сделатьэто все равно.Как бы вы это сделали, учитывая размер в макросе или перечислении PRIVATE_SIZE?

unsigned char obj[PRIVATE_SIZE];

И тогда вы бы передали (void*)obj туда, где это необходимо, верно?Ну, это нарушает правила наложения имен.Хотя вы можете легально получить доступ к любому отдельному символу / байту в любом объекте, вы не можете сделать это наоборот, говоря, что эти символы не являются символами, они просто хранятся за другими типами.То есть юридически нельзя накладывать short int поверх, скажем, obj[2] и obj[3] через броски умных штанов (например, ((struct Private*)obj)->my_short = 2;).Единственный законный способ сделать что-то подобное - через memcpy(), например memcpy(&temp, obj, sizeof temp);, а затем обратно после модификации.Или вам нужно работать с отдельными символами obj[].

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

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

И если вы помещаете экземпляры в специальный раздел данных (см.Атрибут section gcc и скрипты компоновщика), вы можете даже иметь все экземпляры в одном месте (думать, массив) и даже узнать их совокупный размер и, следовательно, подсчитать.

Еще одна вещь, которую нужно сделать, пока неЯвно нарушая любые правила C, вы все равно должны использовать этот unsigned char obj[PRIVATE_SIZE] трюк, но отмывать его, передавая его без изменений через функцию сборки, которую компилятор C не может просмотреть, например что-то вроде

// struct Private* launder(unsigned char*);
.text
.globl launder
launder:
    move %first_param_reg, %return_reg
    ret

Но выВам действительно нужно изменить unsigned char obj[PRIVATE_SIZE] на что-то, что будет иметь правильное выравнивание в вашей архитектуре, например, double obj[PRIVATE_SIZE / sizeof(double)] (или то же самое с long long, если вам так больше нравится).

Что касается PRIVATE_SIZE, вы можете проверить во время компиляции, что он соответствует размеру типа, например

#include "mod.h" // mod.h defines PRIVATE_SIZE
struct Private { ... };
extern char StAtIcAsSeRt[sizeof(struct Private) == PRIVATE_SIZE];
0 голосов
/ 20 февраля 2019

Как представить размер структуры C, не раскрывая его тип?

Если можно немного скомпрометировать: (статически -> main() local)
с Массивы переменной длины (C99), используйте вспомогательную функцию и поместите массив в main().

module.h
  size_t foo_size(void);

main.c
  int main() {
    char m1[foo_size()];
    foo(m1, 10);
  }

Дополнительная работа, необходимая для учета проблем выравнивания.

Рассмотрите возможность ослабления своей цели, как предложено .

0 голосов
/ 20 февраля 2019

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

Так вы пишете приватную инкапсуляцию в C:

  • В файле module.h форвард объявляет неполный тип typedef struct module module;.
  • В module.c поместите определение структуры этой структуры.он будет виден только для module.c, а не для вызывающего.Это известно как непрозрачные типы .
  • Вызывающая сторона может выделять только указатели на эту структуру, никогда не назначать объекты.
  • Код вызывающей стороны может выглядеть следующим образом:

    #include "module.h"
    ...
    module* m;
    result = module_init(&m)
    
  • И функция module_init действует как «конструктор», объявленный в module.h и определенный в module.c:

    bool module_init (module** obj)
    {
      module* m = malloc(sizeof *m);
      ...
      m->something = ...; // init private variables if applicable
    
      *obj = m;
      return true;
    }
    
  • Если вызывающей стороне нужно знать размер объектов, это будет сделано только для печатного копирования и т. Д. Если это необходимо, предоставьте функцию копирования, которая инкапсулирует выделение и копирование («конструктор копирования»)), например:

    result module_copy (module** dst, const module* src);
    

Редактировать:

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...