LINUX.ORG.RU

Как скрыть поля структуры в интерфейсе?

 ,


1

5

ЯП: C99

Пытаюсь запилить shared library. Что имеется:

Файл lib.h (он же интерфейс):

typedef struct mystruct mystruct;

mystruct foo();

Файл lib.c (он же реализация):
#include "lib.h"

struct mystruct {
    float float_val;
    short short_val;
}

mystruct foo() {
    mystruct = {.float_val = 1.1};
    return mystruct;
}


Компилится нормально:
gcc -c lib.c -o lib.o -fPIC
gcc -shared -o lib.so lib.o


Проблема, естественно, состоит в том, что использовать хидер (он же интерфейс) практически невозможно, т.к. размера структуры мы не знаем, и, соответственно, юзер свою прогу с использованием либы скомпилить не сможет:
#include "lib.h"

int main() {
    // ...

    mystruct a = foo(); // вот тут плюнет "error: variable ‘a’ has initializer but incomplete type"
    // затем "invalid use of incomplete typedef ‘mystruct’"
    // а затем "storage size of ‘a’ isn’t known"

    // ...
    return 0;
}


Задача:
1) Юзер имеет только интерфейс. Использует только его и shared object.
2) Юзер не имеет доступа к полям структуры. Т.е. mystruct.float_val = 99.99; выполнить нельзя.

ЛОР, подскажи, пожалуйста.

Может внутрь хидера зашибенить какое-то говно типа
struct blablabla {
    char[sizeof(needed_structure)];
};
?

UPD: Решение - opaque pointer. Возвращать структуру а не указатель на неё - моветон. Если так и делать, то лучше открыть структуру вместо велосипедирования.

★★★★★

Ответ на: комментарий от reprimand

Определи другую структуру с dummy полями такого же размера или вообще массив и возвращай вместо своей секретной структуры.

E ★★★ ()
Ответ на: комментарий от E

Хороший вариант. Правда, к сожалению, придется хардкодить размер структуры, т.к. sizeof() из хидера на нужную структур не особо возможно...

reprimand ★★★★★ ()
Ответ на: комментарий от Deleted

Чтобы пользователь использовал только предоставленное ему API, а не злоупотреблял использованием полей структур.

reprimand ★★★★★ ()
Ответ на: комментарий от reprimand

Можно задать заведомо больший размер структуры, как это делает sockaddr, например.

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

E ★★★ ()
Последнее исправление: E (всего исправлений: 1)
Ответ на: комментарий от reprimand

Ты можешь структуру в хедер засунуть, но при помощи #ifdef спрятать её от пользователя (например, под другим названием, вариантов много можно придумать).

NeXTSTEP ★★ ()
Ответ на: комментарий от E

У меня структура не будет частью массивов, посему имеет смысл использовать её а-ля переменная.

Да, насчёт FILE... Там как-то хитро сделали

typedef struct _IO_FILE FILE;
при этом в соответствующем файле
struct _IO_FILE {
  int _flags;
...
}
Т.е. структура есть, она якобы должна быть доступна, но нет, доступа нет. Чего я не понимаю?

reprimand ★★★★★ ()
Последнее исправление: reprimand (всего исправлений: 2)
Ответ на: комментарий от NeXTSTEP

Ты можешь структуру в хедер засунуть, но при помощи #ifdef спрятать её от пользователя (например, под другим названием, вариантов много можно придумать).

Вот тут не понял. Можно подробнее?

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

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

reprimand ★★★★★ ()
Последнее исправление: reprimand (всего исправлений: 1)

Можешь сделать char[] вместо данных в структуре. Но не забывай: возвращать структуру в публичном апи - UB. And all about fields is implementation specific. В общем - или указатели или твоя либа/аппа/интерфейс - говно.

mittorn ★★★★★ ()
Ответ на: комментарий от reprimand

Например, так:

struct _my_struct {
	...
};
#ifdef LIBRARY_IMPLEMENTATION
typedef struct _my_struct my_struct;
#else
typedef struct {
	struct _my_struct _do_not_touch_this_please;
} my_struct;
#endif

sizeof(my_struct) и в коде библиотеки, и у пользователя одинаковым будет. Но при этом пользователь не сможет к полям напрямую обращаться.

NeXTSTEP ★★ ()
Ответ на: комментарий от reprimand

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

Deleted ()
Ответ на: комментарий от mittorn

На самом деле у меня либа уже работает с указателями. Только одна функция прямо возвращает структуру...

Мне влом не столько допиливать либу, сколько уже написанные тесты.

reprimand ★★★★★ ()

В общем, сделал так.

1) Для компиляции либы использую lib_real.h
2) Юзеру даю lib.h, в котором структура имеет поля типа void * в нужном количестве дабы соответствовать размеру оригинальной структуры (благо, там используются типы, благодаря которым с изменением архитектуры на 32 битную ничего не сломается, лол).
3) В комментариях написал, что это злостынй хак, и вообще я, нехороший человек, когда-нибудь допишу либу когда я снова убью в себе лень.

reprimand ★★★★★ ()
Ответ на: комментарий от reprimand

учти такую вещь. Если эта либа скомпилирована одним компилятором, а то что её использует другим - будет ахтыдолбанажтыматерь. У меня к примеру такая конфигурация для msvc и mingw работает под некоторыми виндами, но не всеми. А ещё она не работанет под wine.

mittorn ★★★★★ ()

Возвращай пользователю указатели на структуры и пусть работает с ними. И массив структур сделать не получится, пользователю придётся делать массив указателей на них, чтобы адресная арифметика работала.

evilface ★★ ()
Ответ на: комментарий от mittorn

окей, смотри

приватная структура

struct {
    char *whatever;
    size_t whatever;
    size_t whatever;
    size_t whatever;
    char *whatever;
    blablabla *whatever;
}

публичная структура
struct {
   void *whatever;
   void *whatever;
   void *whatever;
   void *whatever;
   void *whatever;
   void *whatever;
}

Что может случиться плохого?

reprimand ★★★★★ ()

1) Юзер имеет только интерфейс. Использует только его и shared object.

2) Юзер не имеет доступа к полям структуры. Т.е. mystruct.float_val = 99.99; выполнить нельзя.

Либо используй указатели и создание объектов в куче, типа

mystruct* create_mystruct() {
    mystruct* obj = malloc(sizeof(mystruct));
    obj->float_val = 1.1;
    return obj;
}

void destroy_mystruct(mystruct* obj) {
    free(obj);
}

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

korvin_ ★★★★★ ()
Последнее исправление: korvin_ (всего исправлений: 1)
Ответ на: комментарий от reprimand

return любая структура
Брат умер от внезапного агальноно проникновения, которое спровоцировал компилятор соответствуюший всем стандартам

mittorn ★★★★★ ()

Но зачем?

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

Вопрос приватности — это ООП говнина, которая мало влияет на архитектуру конечного приложения или либы.

Попытка использовать недокументированые возможности — это ошибка разработки.

Ты не можешь нести за это ответственность. Вместо «сидеть и думать как защитить дебила от его самого», писал бы функциоционал дальше — больше пользы будет.

deep-purple ★★★★★ ()

В крестах для этого юзают pimpl, для Си тож подойдет, не знаю, насколько идиоматично. // на самом деле не совсем для этого, ну лан

Kuzy ★★★ ()
Последнее исправление: Kuzy (всего исправлений: 1)
Ответ на: комментарий от reprimand

Что может случиться плохого?

Плохое уже случилось.

sizeof(size_t) не обязан быть равен sizeof(void *).

И не надо делать структуру возвращаемым значением функции или её аргументом. Лучше сразу переделать такое API, пока не поздно.

Sorcerer ★★★★★ ()

Сделай как в udev сделали. Возвращай указатель на структуру с forward declaration типом, а также набор функций для работы с ней, а сами поля пряч у себя под ковром.

Gorthauer ★★★★★ ()
Ответ на: комментарий от korvin_

man opaque pointer

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

+1.

PS:

typedef struct mystruct mystruct;

Это ужасно.

Stil ★★★★★ ()

Что-то по стилю pimpl:

lib.h:

struct _blablabla_private {
    char *whatever1;
    size_t whatever2;
    size_t whatever3;
    size_t whatever4;
    char *whatever5;
    int *whatever6; 
};
 
struct blablabla {
   char data_[sizeof(struct _blablabla_private)];
};

http://ideone.com/xCUrgT

Если сильно надо, то _data можно сделать указателем на forward-declared тип (а можно вообще void*, чтоб никто не догадался).

// А если вообще хочется zajebiście, то используй С++

KennyMinigun ★★★★★ ()
Последнее исправление: KennyMinigun (всего исправлений: 1)
Ответ на: комментарий от reprimand

Чтобы пользователь использовал только предоставленное ему API, а не злоупотреблял использованием полей структур.

Зачем? Почему б просто не написать в документации, что напрямую лезть в структуру нельзя?

Впрочем, я уже по этому поводу что-то писал Как задать «полупрозрачную» структуру не повторяясь? (комментарий)

SZT ★★★★★ ()

Плюсую предложение перестать выкобениваться и заюзать opaque pointer.

Pavval ★★★★★ ()
Последнее исправление: Pavval (всего исправлений: 1)

Либо используй указатели и создание объектов в куче, либо открывай структуру.

this

Common practice для этого случая называется «opaque pointer», см например «FILE *».

anonymous ()

Имхо, эту тему стоит почитать всем, кто не понимает, зачем нужен C++.

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

hobbit ★★★★★ ()
Ответ на: комментарий от reprimand

Но ради чего? В сишечке возвращать структуры не принято. Либо возвращать указатели, либо функция должна принимать указатель на структуру.

MyStruct s;
MyStruct_init(&s, ...);

или

MyStruct *s = MyStruct_init(...);

А вот именно прямо возвращать структуру - плохо. Это тебе не C++.

KivApple ★★★★★ ()
Ответ на: комментарий от hobbit

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

С плюсами тоже. ТСу хватило бы питона, инфа соточка.

A1 ()

Достаточно надежно:

//lib.h
struct pub {
  int dummy1;
  float dummy2;
};

// lib.c
#define dummy1 name1
#define dymmy2 name2
#include "lib.h"
А вообще, открыть структуру, правильно написать документацию и забить на идиотов. jmp_buf - хороший пример.

anonymous ()
Ответ на: комментарий от KivApple

возвращать структуру - плохо

Не плохо. В типичной реализации - то же самое что передача структуры по указателю.

anonymous ()
Ответ на: комментарий от hobbit

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

ты так говоришь, как будто в крестах структуры какие-то другие, и скрывать их кишки надо как-то не так, как в сишке.

крестосрач разводить не хочу, но ты ерунду написал.

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

waker ★★★★★ ()

Благодарю всем отметившимся.

Всё-таки решил сделать правильно сразу - возвращать указатель.

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

reprimand ★★★★★ ()
Ответ на: комментарий от hobbit

Ну чего бы хватило ТСу, я гадать не хочу, вдруг он как раз под контроллеры пишет...

Нет, прикладной софт.

// несмотря на то, что в рабоее время я Embedded C developer, на самом деле на целевых девайсах стоит linux, и софт, соответственно, не такой уж и embedded. Т.ч. каким-то чудом контроллеры меня обходят стороной...

reprimand ★★★★★ ()
Последнее исправление: reprimand (всего исправлений: 1)
Ответ на: комментарий от hobbit

Имхо, эту тему стоит почитать всем, кто не понимает, зачем нужен C++.

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

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

Ну, у меня иной путь...

Относительно C++ у меня мнение совпадает с Столлманом.

reprimand ★★★★★ ()
Ответ на: комментарий от waker

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

Да, в сишке, в принципе, можно сделать нечто аналогичное, но с куда большим количеством страданий.

но если так хочется блеснуть интеллектом

Нет, такой задачи не ставил :)

hobbit ★★★★★ ()
Ответ на: комментарий от hobbit

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

и тебе пофигу, что это не совпадает с условиями задачи?

// я бы тоже вернул указатель, например void *, с typedef на удобный тип, или на пустую структуру, или на структуру только с публичными полями, или еще как-нибудь. и это нисколько не сложнее, по сравнению с крестами.

waker ★★★★★ ()
Последнее исправление: waker (всего исправлений: 1)
Ответ на: комментарий от waker

и тебе пофигу, что это не совпадает с условиями задачи?

Где не совпадает? Ты про то, что ТС пытался вернуть структуру вместо указателя?

hobbit ★★★★★ ()
Ответ на: комментарий от reprimand

Относительно C++ у меня мнение совпадает с Столлманом.

Ты про это?

Больше всего ему нравятся Lisp и C, тогда как C++ он считает «довольно уродливым».

http://www.computerra.ru/28331/richard-stollman-rasskazal-o-tom-kakoy/

В оригинале - «By contrast, I find C++ quite ugly», но развёрнутого обоснования, почему ugly, я в публикациях про RMS не нашёл, к сожалению.

hobbit ★★★★★ ()
Последнее исправление: hobbit (всего исправлений: 1)
Ответ на: комментарий от hobbit

А у него развёрнутого обоснования и нет.

Суть в том, что я действительно считаю плюсы тошнотворными. От чтения кода на плюсах мне становится противно, особенно когда начинается что-то бОльшее, чем просто «си с классами».

reprimand ★★★★★ ()
Ответ на: комментарий от reprimand

От чтения кода на плюсах мне становится противно

Странно, а мне наоборот, ООП-программа на C++ кажется куда более изящной, чем наколенные реализации псевдоООП на Си, без которого не обойтись, например, при реализации оконного GUI (примеры - win32, GTK+). Коллбэки, дескрипторы, куча глобальных функций...

особенно когда начинается что-то бОльшее, чем просто «си с классами».

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

hobbit ★★★★★ ()
Последнее исправление: hobbit (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.