Как сделать определение типа максимально приватным без использования Malloc?

Я ищу способ сделать определения типов частного стиля, к которым можно получить доступ или управлять ими только из определенного набора вызовов функций (setBit (bit_typ * const t), getBit (bit_typ * const t)). Я ищу способ сделать это без использования malloc, есть ли у кого-нибудь идеи?

EDIT:// этот вопрос отличается от этого, потому что он поиск способов максимально приблизиться к «частной» структуре, тогда как другой вопрос (TL; DR, есть ли способ определить непрозрачный тип, который, тем не менее, может быть размещен в стеке и без нарушения строгого правила псевдонимов?) ищет решение к проблеме, связанной с одним из возможных решений моего вопроса.


c plc
person jonbovy    schedule 06.07.2015    source источник
comment
Какое отношение malloc имеет к области видимости typedefs?!   -  person ikegami    schedule 06.07.2015
comment
Объявляйте только typedef в файлах .c, которые определяют конкретный набор вызовов функций.   -  person chux - Reinstate Monica    schedule 06.07.2015
comment
Есть похожие вопросы, на которые ответили с помощью malloc для выделения памяти, это решение мне не подойдет.   -  person jonbovy    schedule 06.07.2015
comment
Malloc не имеет абсолютно никакого отношения к конфиденциальности. На самом деле, C сам по себе не позволяет делать вещи приватными, хотя вы можете попытаться сделать их непрозрачными.   -  person David Hoelzer    schedule 06.07.2015
comment
@Weather Vane: Да, но все дело в том, что bit_typ должен быть доступен только как предварительное объявление typedef struct bit_typ bit_typ; - типичный непрозрачный typedef. К сожалению, это не позволяет пользователю определять объекты типа bit_typ. Пользователь должен вызвать функцию API (которая знает все о bit_typ), которая malloc изменит объект. Это malloc, о котором говорит OP, и поэтому его часто упоминают в связи с непрозрачными типами. malloc — это цена, которую мы должны заплатить за полную непрозрачность. Обойти это невозможно, если только вы не пойдете так, как я описываю ниже.   -  person AnT    schedule 06.07.2015
comment
возможный дубликат непрозрачных типов, размещаемых в стеке в C   -  person ecatmur    schedule 06.07.2015


Ответы (1)


Один из способов сделать это — показать общий размер непрозрачного типа и заставить его объявить объекты вашего непрозрачного типа как unsigned char [N] буферы. Например, допустим, у вас есть некий тип OpaqueType, внутренности которого вы хотите скрыть от пользователя.

В заголовочном файле (представленном пользователю) вы делаете это

typedef unsigned char OpaqueType[16];

где 16 — это точный размер в байтах типа, который вы хотите скрыть. В заголовочном файле вы пишете весь интерфейс с точки зрения этого типа, например.

void set_data(OpaqueType *dst, int data);

В файле реализации вы объявляете фактический тип

typedef struct OpaqueTypeImpl
{
  int data1;
  double data2;
} OpaqueTypeImpl;

и реализовать функции следующим образом

void set_data(OpaqueType *dst, int data)
{
  OpaqueTypeImpl *actual_dst = (OpaqueTypeImpl *) dst;
  actual_dst->data1 = data;
}

Вы также можете добавить статическое утверждение, которое будет гарантировать, что sizeof(OpaqueType) совпадает с sizeof(OpaqueTypeImpl).

Конечно, как было отмечено в комментариях ниже, необходимо предпринять дополнительные шаги, чтобы обеспечить правильное выравнивание таких объектов, например _Alignas в C11 или какую-либо технику на основе объединения в «классическом» C.

Таким образом, вы даете пользователю возможность объявить нединамический объект OpaqueType, то есть вы не заставляете пользователя вызывать вашу функцию, которая будет malloc таких объектов внутри. И в то же время вы ничего не показываете пользователю о внутренней структуре вашего типа (кроме его общего размера и требований к выравниванию).

Также обратите внимание, что OpaqueType, объявленный таким образом, является массивом, а это означает, что его нельзя скопировать (если только вы не используете memcpy). Это может быть полезно, если вы хотите активно предотвращать неограниченное копирование на уровне пользователя. Но если вы хотите включить копирование, вы можете обернуть массив в структуру.

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

person AnT    schedule 06.07.2015
comment
Это действительно не сработает; вы не можете использовать псевдонимы с чем-либо, кроме типов char. Используйте alignas (C11) или аналогичный. - person ecatmur; 06.07.2015
comment
@ecatmur: Неправда. Чтобы использовать псевдоним, вам необходимо получить доступ к данным через тип OpaqueType. Однако при таком подходе вы никогда не получите к нему доступ как OpaqueType. Все обращения должны осуществляться через OpaqueTypeImpl после кастинга. Это не алиасинг. - person AnT; 06.07.2015
comment
Я был бы очень осторожен в этом; вы нарушаете 6.5p6: эффективный тип объекта для доступа к его хранимому значению — это объявленный тип объекта, если он есть. По общему признанию, использование буфера char сталкивается с той же проблемой, но компилятор гораздо менее вероятно будет использовать поведение undefined в этом случае. - person ecatmur; 06.07.2015
comment
@ecatmur: Я не понимаю, как такой строгий подход к алиасингу и UB может сосуществовать с разрешением на доступ к неактивным членам объединения, которое было введено в язык в TC3 для C99. - person AnT; 06.07.2015
comment
AIUI для доступа к неактивному члену объединения вы должны сделать это через lvalue типа объединения, поэтому компилятор знает, что нужно ослабить ограничения на псевдонимы (альтернативно: отключить оптимизацию) для этого доступа. - person ecatmur; 06.07.2015
comment
@ecatmur: Хороший вопрос. Кажется, это так ограничительно. Возникает интересный вопрос: что, если я захочу реализовать свой собственный высоко локализованный распределитель, который будет управлять пользовательскими блоками памяти внутри статически объявленной области хранения unsigned char storage[2048];. Массив не выделен, что означает, что он имеет определенный эффективный тип. Похоже, что нет никакого способа легально реализовать мой распределитель в C, так как в любом случае я в конечном итоге получаю доступ к объекту с помощью эффективный тип unsigned char как объекты какого-либо другого типа. - person AnT; 06.07.2015
comment
Да, это делает невозможным создание распределителя на основе статического буфера, поэтому я был бы очень удивлен, если бы компилятор полностью применил правила псевдонимов для символьного буфера. Также следует учитывать совместимость с С++, например. std::aligned_storage. - person ecatmur; 07.07.2015