совместимость типа параметра функции

Я скрываю некоторые поля структуры из типов struct, чтобы сделать заголовки общедоступного API более понятными.

Сначала я использовал этот стиль, чтобы скрыть (на самом деле не скрывать, просто отделить от общедоступных редактируемых элементов) некоторые элементы,

include/scene.h

typedef struct ScenePrivateFields {
  SceneFlags flags;
  /* other members */
} ScenePrivateFields;

typedef struct Scene {
  ScenePrivateFields _priv;
  RootNode           rootNode;
  /* other members */
} Scene;

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

В конце концов я перешел на другой стиль

include/scene.h

typedef struct Scene {
  RootNode rootNode;
  /* other members */
} Scene;

EXPORT
Scene*
allocScene(void);

src/types/impl_scene.h

typedef struct SceneImpl {
  Scene      pub;
  SceneFlags flags;
  /* other members */
} SceneImpl;

например, если у меня есть такая функция:

include/scene.h

void
renderScene(Scene * __restrict scene, /* other params */);

Мне нужно передать сцену в SceneImpl, чтобы получить доступ к закрытым полям. Я делаю это так:

src/scene/scene.c

void
renderScene(Scene * __restrict scene, /* other params */) {
  SceneImpl *sceneImpl;

  sceneImpl = (SceneImpl *)scene;
}

чтобы избежать приведения каждого вызова функции, я подумал, что, возможно, я смогу сделать что-то вроде этого, если это законно и не нарушает стандарты C:

src/scene/scene.c

void
renderScene(SceneImpl * __restrict sceneImpl, /* other params */) {
  /* skip casting scene to sceneImpl */
}

Поскольку Scene является первым членом SceneImpl, могу ли я определить общедоступный api (функцию) с помощью Scene и определить реализацию (функцию) с помощью SceneImpl. Я думаю, это сработает, потому что оба являются указателями, это правильная или хорошая идея?

ПРИМЕЧАНИЕ: я компилирую с -fstrict-aliasing

РЕДАКТИРОВАТЬ: FWIW, здесь реализация функции выделения, пользователи должны использовать эту функцию для выделения структуры:

EXPORT
Scene*
allocScene(void) {
  SceneImpl *sceneImpl;

  sceneImpl = calloc(1, sizeof(*sceneImpl));

  /* initialize pulic part */
  sceneImpl->pub.field1 = ...

    /* initialize private part */
  sceneImpl->priv_field1 = ...

  /* return public part */
  return &sceneImpl->pub;
}

person recp    schedule 27.11.2017    source источник


Ответы (2)


Вы можете использовать непрозрачный тип для приватных данных.

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

// forward declaration of struct ScenePrivateFields 
typedef struct ScenePrivateFields ScenePrivateFields;

typedef struct Scene {
  ScenePrivateFields *_priv;    // now a pointer
  RootNode           rootNode;
  /* other members */
} Scene;

Затем в вашем файле реализации вы определяете частную структуру:

struct ScenePrivateFields {
  SceneFlags flags;
  /* other members */
}

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

person dbush    schedule 27.11.2017
comment
Я не хочу выделять дополнительное пространство для частных полей, потому что он будет извлекать другую область памяти, я думаю, что непрерывная память будет лучше из-за локальности кеша - person recp; 27.11.2017
comment
@recp Вы не должны беспокоиться об этом уровне оптимизации, если у вас нет особой причины для этого. Лучше придерживаться четко определенного поведения вместо хаков, которые могут не сработать. - person dbush; 27.11.2017
comment
Я думаю, что мои типы структур не хакерские, не так ли? но я думаю о взломе вызовов функций :) это актуальный вопрос: вызовы функций были бы взломаны или нет: / - person recp; 27.11.2017
comment
@recp Преобразование одного типа структуры в другой, даже если одна содержит другой, - это хитрость. В любом случае вам понадобится функция для заполнения частных полей, поэтому лучше сделать это четко определенным способом, который фактически обеспечивает конфиденциальность поля. - person dbush; 27.11.2017
comment
возможно ты прав, потому что я тоже использую строгий псевдоним. Но согласно этому stackoverflow.com / questions / 8416417 /, мои структуры кажутся хорошо определенными, потому что A является первым членом B, а не случайно расположен внутри него, но я не уверен в вызове функции, если это законно и не взломано, это избавило бы меня от слишком большого количества базовых типов для производных отливок, если это законно - person recp; 27.11.2017
comment
Я не хочу выделять дополнительное пространство для частных полей, потому что он будет извлекать другую область памяти, я думаю, что непрерывная память будет лучше из-за локальности кеша У вас не может быть как частных полей, так и непрерывной памяти с непубличные поля. Чтобы сделать их непрерывными, вы должны раскрыть то, что находится внутри структуры, которую вы хотите сохранить. Компилятор должен знать, что находится внутри struct ScenePrivateFields, чтобы поместить эту структуру в struct Scene. - person Andrew Henle; 27.11.2017
comment
@AndrewHenle проверяет определение SceneImpl, все члены видны компилятору, и им выделяются общедоступные члены с одним вызовом calloc / malloc. Частные члены не отображаются, но предоставляется настраиваемая функция выделения, она выделяет SceneImpl и возвращает & sceneImpl- ›pub; Таким образом, пользователи могут использовать эту структуру только как указатель, они не могут размещать ее статически. Также все члены ScenePrivateFields были общедоступными / открытыми, но сейчас я использую SceneImpl (который полностью закрыт). - person recp; 28.11.2017
comment
@recp Не сработает. Вы не можете получить доступ к каким-либо полям struct, не зная, где находится каждое поле. Вы не можете получить доступ к какой-либо части typedef struct Scene { ScenePrivateFields _priv;..., не зная, что находится в ScenePrivateFields. Нельзя встраивать непрозрачную структуру внутрь непрозрачной. Все, что вы можете сделать, это указать в публичной структуре указатель на непрозрачную. Если структура непрозрачна, вы даже не можете сказать, сколько байтов занимает ее экземпляр, так как же вы можете встроить ее в общедоступную структуру? - person Andrew Henle; 28.11.2017
comment
@AndrewHenle Я получаю доступ к закрытым полям в моей папке src /, и пользователи не должны пытаться получить к ним доступ. Я отредактировал свой вопрос и предоставил alloc func. Все члены видны при выделении и при частном использовании закрытых полей :) закрытые поля находятся на уровне библиотеки. Если я хочу использовать частные поля, я импортирую дополнительный заголовок src / types / scene_impl.h. Таким образом, все члены видны с расширенной структурой (Scene + SceneImpl) в библиотеке - person recp; 28.11.2017
comment
@recp Распределение не имеет значения. Учитывая typedef struct Scene { ScenePrivateFields _priv; RootNode rootNode;..., вы не можете получить доступ к полям в struct Scene, не зная, какие поля находятся в struct ScenePrivateFields. Идите вперед и попробуйте написать код, который это сделает. Вы не можете писать код C, который может иметь доступ к rootNode, если он также не имеет доступа к содержимому struct ScenePrivateFields. - person Andrew Henle; 28.11.2017
comment
@AndrewHenle Я не думаю, что он сейчас на месте. Прямо сейчас он динамически создает частную структуру, первый член которой является публичной структурой, и возвращает указатель на публичный член. Остальные библиотечные функции затем преобразуют публичный указатель в частный указатель. - person dbush; 28.11.2017
comment
@AndrewHenle На основании связанного вопроса дальше в цепочке комментариев это должно быть разрешено, но похоже на взлом. - person dbush; 28.11.2017
comment
@dbush Это взлом, и это также опасный способ взлома. Любой пользователь, который sizeof() работает со структурой, или пытается создать из них массив, или выполняет арифметические действия с указателем на структуру, ждет неприятный сюрприз. Либо сделайте всю структуру непрозрачной и недоступной напрямую, сделайте ее полностью открытой и исключите необходимость в частном распределителе, либо используйте составную структуру - общедоступную с указателем на непрозрачную структуру. Скрытие непрозрачной части после публичной добавляет ненужные ограничения, которые открывают дверь для катастрофических ошибок, если вызывающий их нарушает. - person Andrew Henle; 28.11.2017
comment
@AndrewHenle, это может быть опасно, но взломать? Поскольку у структуры есть настраиваемый распределитель, как пользователи могут создать из них массив, пропустить распределитель и инициализировать его статически? Возможно, мне следует создать собственный размер, тогда он исправит всю арифметику sizeof () и указателя. Я могу вернуть частные поля обратно в общедоступный заголовок, как и в предыдущем случае, но я не уверен, а также сделать его полностью недоступным, что приведет к множеству вызовов функций для доступа ко всем членам (плюс также проверка параметров). Это не просто ограничения, это делает общедоступные заголовки более понятными и отделяет частную работу на уровне библиотеки от уровня общедоступного API. - person recp; 28.11.2017
comment
@recp Если вы предоставите полный struct Scene, любой может сделать struct Scene *p = malloc( sizeof( *p ) ); или struct Scene sceneArray[1234];. Ваш частный распределитель не может этого предотвратить. И учитывая struct Scene *p = privAllocator();, вы тоже не можете предотвратить p++;. И то, и другое вызовет серьезные проблемы с предложенным вами решением. Может быть, мне стоит создать нестандартный размер Как вы думаете, вы можете это сделать? Если вы хотите, чтобы общедоступные заголовки оставались простыми, труднее быть проще и безопаснее, чем непрозрачная структура с функциями получения и установки. - person Andrew Henle; 28.11.2017
comment
@AndrewHenle: Должны ли реализации, stdio.h которых включает полное определение структуры для FILE, быть дефектными, потому что такое объявление синтаксически допускает код, подобный FILE my_file = *fopen("fred","r"); fprintf(&my_file, "Hello\n");, даже если его поведение может быть совершенно бессмысленным? - person supercat; 06.12.2017

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

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

Если нет необходимости, чтобы «пользовательский» код видел завершенный тип структуры, можно указать в файле заголовка просто сказать typedef struct tagName typeName;, а затем использовать указатели типа typeName*. К сожалению, иногда может потребоваться сделать что-то, что потребовало бы наличия полного типа, даже если пользовательскому коду не нужно было бы фактически обращаться к большинству его членов. Если это необходимо, я бы предположил, что лучше использовать любые параметры компилятора, которые потребуются, чтобы сообщить компилятору, что он должен допускать возможность того, что указатель одного типа структуры может использоваться для доступа к членам другого, который имеет подходящий макет [-fno-strict-aliasing на gcc или clang], чем обойти желание авторов компилятора обработать выпотрошенную версию языка с удаленными такими полезными возможностями.

person supercat    schedule 28.11.2017