Публичный sizeof для частной структуры

У меня есть небольшой модуль сокрытия данных, который выглядит так:

/** mydata.h */
struct _mystruct_t;
typedef struct _mystruct_t mystruct;

mystruct *newMystruct();
void freeMystruct( mystruct** p );

/** mydata.c */
#include "mydata.h"
struct _mystruct_t {
    int64_t data1;
    int16_t data2;
    int16_t data3;
};
// ... related definitions ... //

По большей части, это то, что я хочу; несмотря на простоту, структура имеет строгие требования к согласованности, и я действительно не хочу предоставлять доступ к элементам данных.

Проблема в том, что в клиентском коде я хотел бы включить структуру в другую структуру, которую я хотел бы разместить в стеке. Прямо сейчас я прыгаю через обручи, чтобы освободить mystruct* в каком-то клиентском коде. Поскольку а) mystruct довольно мала, и я действительно не думаю, что она станет большой в ближайшее время, и б) это не проблема, что клиентский код должен перекомпилироваться, если я когда-либо изменю mystruct, я хотел бы сделать размер mystruct общедоступным (то есть в шапке).

Две возможности, которые я рассмотрел:

/** mydata.h */
typedef struct {
    // SERIOUSLY DON'T ACCESS THESE MEMBERS
    int64_t data1;
    int16_t data2;
    int16_t data3;
} mystruct;

Я думаю, недостатки здесь говорят сами за себя.

OR

/** mydata.h */
#define SIZEOF_MYSTRUCT (sizeof(int64_t)+sizeof(int16_t)+sizeof(int16_t))
// everything else same as before...

/** mydata.c */
// same as before...
_Static_assert (SIZEOF_MYSTRUCT == sizeof(mystruct), "SIZEOF_MYSTRUCT is incorrect")

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

Является ли один из них предпочтительным? Или, что еще лучше, есть ли какой-то хитрый трюк, чтобы предоставить фактическое определение структуры в заголовке, а позже каким-то образом скрыть возможность доступа к членам?


person Luis    schedule 03.10.2014    source источник
comment
Не используйте имена, начинающиеся с подчеркивания. Большинство таких имен зарезервированы для реализации стандартом C. Есть нюансы в формулировках определения (раздел 7.1.3 Зарезервированные имена), но простое правило «не использовать начальные символы подчеркивания» избавит вас от неприятностей.   -  person Jonathan Leffler    schedule 03.10.2014
comment
Я думал, что это просто подчеркивание, за которым следует заглавная буква?   -  person Luis    schedule 03.10.2014
comment
Короткий ответ: вы не должны встраивать структуру в другую; вы можете вставить указатель на структуру в другой.   -  person Jonathan Leffler    schedule 03.10.2014
comment
Ты подумал неправильно. Один из двух пунктов списка говорит следующее: — Все идентификаторы, начинающиеся с символа подчеркивания и прописной буквы или другого символа подчеркивания, всегда зарезервированы для любого использования. так что вы правы, что заглавная буква подчеркивания зарезервирована. Однако следующий пункт: — Все идентификаторы, начинающиеся со знака подчеркивания, всегда зарезервированы для использования в качестве идентификаторов с файловой областью как в обычном пространстве имен, так и в пространстве имен тегов.   -  person Jonathan Leffler    schedule 03.10.2014
comment
ой, спасибо за исправление, теперь вернемся к коду, который я ранее написал с ведущими символами подчеркивания... :P   -  person Luis    schedule 03.10.2014


Ответы (3)


Вы можете создать другой .h файл, распространяемый конечному пользователю, который будет определять вашу секретную структуру точно так же, как массив байтов (вы не можете скрыть данные без криптографии/контрольной суммы, кроме как просто сказать "вот несколько байтов"):

typedef struct {
    unsigned char data[12];
} your_struct;

Вам просто нужно убедиться, что обе структуры одинаковы для всех компиляторов и параметров, поэтому используйте __declspec(align()) (для VC) в коде вашей библиотеки, например:

// Client side
__declspec(align(32)) typedef struct {
    int64_t data1;
    int16_t data2;
    int16_t data3;
} mystruct;

Чтобы структура не имела длину 16 байт вместо обычно ожидаемых 12 байт. Или просто используйте параметр компилятора /Zp.

person Vyktor    schedule 03.10.2014

Я бы остановился на сгенерированном времени настройки #define, описывающем размер mystruct и, возможно, typedef char[SIZEOF_MYSTRUCT] opaque_mystruct, чтобы упростить создание заполнителей для mystruct.

Вероятно, идея configure time actions заслуживает некоторых пояснений. Общая идея состоит в том, чтобы

  1. поместите определение mystruct в частный, неэкспортируемый, но, тем не менее, распределенный заголовок,
  2. создайте небольшое тестовое приложение, которое будет собираться и выполняться перед библиотекой. Тестовое приложение будет включать закрытый заголовок и печатать фактические sizeof (mystruct) для данного компилятора и параметров компиляции.
  3. создайте соответствующий скрипт, который создаст библиотеку config.h с #define SIZEOF_MYSTRUCT <calculated_number> и, возможно, определением opaque_mystruct.

Эти шаги удобно автоматизировать приличной системой сборки, например cmake, gnu autotools или любой другой с поддержкой стадии configure. На самом деле все упомянутые системы имеют встроенные средства, которые упрощают всю задачу до вызова нескольких предопределенных макросов.

person user3159253    schedule 03.10.2014

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

/** in mydata.h */
typedef const struct { const char data[12]; } mystruct;
mystruct createMystruct();
int16_t exampleMystructGetter( mystruct *p );
// other func decls operating on mystruct ...


/** in mydata.c */
typedef union {
    mystruct public_block;
    struct mystruct_data_s {
        int64_t d1;
        int16_t d2
        int16_t d3;
    } data;
} mystruct_data;

// Optionally use '==' instead of '<=' to force minimal space usage
_Static_assert (sizeof(struct mystruct_data_s) <= sizeof(mystruct), "mystruct not big enough");

mystruct createMystruct(){
    static mystruct_data mystruct_blank = { .data = { .d1 = 1, .d2 = 2, .d3 = 3 } };
    return mystruct_blank.public_block;
}

int16_t exampleMystructGetter(mystruct *p) {
    mystruct_data *a = (mystruct_data*)p;
    return a->data.d2;
}

В gcc 4.7.3 компилируется без предупреждений. Простая тестовая программа для создания и доступа через геттер также компилируется и работает должным образом.

person Luis    schedule 03.10.2014
comment
Два других ответа были хорошими, поэтому я проголосовал, но если кто-то не придет с хорошей критикой, я приму свой ответ, поскольку я думаю, что он самый простой и решает все мои проблемы. - person Luis; 03.10.2014
comment
Вы можете применить p непосредственно к struct mystruct_data_s - person M.M; 04.10.2014
comment
В этом коде есть проблема, из-за которой mystruct может быть неправильно выровнено для struct mystruct_data_s. Вы можете использовать спецификаторы выравнивания C11, чтобы исправить это, и/или обновить свой _Static_assert, чтобы убедиться, что оба имеют одинаковые требования к выравниванию. - person M.M; 04.10.2014
comment
Выравнивание является ключевым моментом. Как правило, без дополнительных предположений о компиляторе и/или целевой платформе вы не можете быть уверены в том, какое выравнивание подходит для вашей структуры. Даже если вы выровняете его по байтам, словам или двойным словам и используете параметры выравнивания для портов (например, специфичные для С++ 11), вы не можете быть на 100% уверены, что выбранное выравнивание будет правильным для данной аппаратной платформы и оптимальным. (по размеру, производительности и т. д.) для платформы и приложения: некоторые приложения могут нуждаться в оптимизации по размеру, другие — по скорости и т. д., и такие вещи редко должны решаться в библиотеке. уровень. - person user3159253; 04.10.2014
comment
Вот почему я предложил решение с configure-time config.h и целевыми конфигурациями - person user3159253; 04.10.2014
comment
Если я проверю _Static_assert, что sizeof(mystruct) == sizeof(mystruct_data) этого будет достаточно? Тогда, казалось бы, гарантируется, что они всегда будут ссылаться на один и тот же блок памяти, а выравнивание относительно mystruct_data_s обеспечивается путем обработки объединения. - person Luis; 04.10.2014
comment
Я считаю, что ваше последнее утверждение return, строго говоря, является нарушением строгого сглаживания, доступ к объекту mystruct осуществляется через lvalue типа mystruct_data. Я не уверен, что это может иметь значение здесь (то есть, если есть возможности оптимизации). - person mafso; 05.10.2014
comment
Но зачем эти сложные причуды? Поместите полный спецификатор структуры в заголовок (вы сказали, что перекомпиляция не является проблемой, поэтому я не вижу причин не делать этого), и все готово. Если вы не хотите, чтобы к членам был доступ извне, просто не документируйте их. Мне пришлось посмотреть на код в течение нескольких минут, чтобы увидеть, что он делает, безопасен ли он (а у меня все еще есть сомнения), есть ли части, которые при изменении других частей также нуждаются в изменении и т. д. Полная структура в заголовку потребовалось бы меньше секунды, чтобы я понял, что он делает. - person mafso; 05.10.2014