C скрытие структуры данных (непрозрачный указатель) - PullRequest
2 голосов
/ 31 января 2020

В настоящее время я немного озадачен концепцией сокрытия информации C -структур.

Фон этого вопроса - встроенный c проект с почти нулевым знанием OOP.

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

Но после проверки MISRA- C я обнаружил предупреждение средней серьезности: MISRAC2012-Dir-4.8 - реализация структуры подвергается излишнему раскрытию к единице перевода.

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

Я быстро попробовал простой пример, который выглядит следующим образом:

struct_test.h

//struct _structName;

typedef struct _structName structType_t;

struct_test. c

#include "struct_test.h"

typedef struct _structName
{
    int varA;
    int varB;
    char varC;
}structType_t;

main. c

#include "struct_test.h"

structType_t myTest;

myTest.varA = 0;
myTest.varB = 1;
myTest.varC = 'c';

Это приводит к ошибке компилятора, что для main. c Размер myTest неизвестен. И, конечно, это так. Main. c знает только, что существует структура типа structType_t и ничего больше.

Поэтому я продолжил свое исследование и наткнулся на концепцию непрозрачных указателей.

Итак, я попробовал вторую попытку:

struct_test.h

typedef struct _structName *myStruct_t;

struct_test. c

#include "struct_test.h"

typedef struct _structName
{
    int varA;
    int varB;
    char varC;
}structType_t;

main. c

#include "struct_test.h"

myStruct_t myTest;

myTest->varA = 1;

И я получаю ошибку компилятора: разыменование указателя на неполный тип struct _structName

Так что, очевидно, я не понял основную c концепцию этого метода. Моя главная путаница заключается в том, где будут находиться данные объекта struct?

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

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

Я взял идею из этого поста: Что такое непрозрачный указатель в C?

В сообщении упоминается, что доступ обрабатывается с помощью методов интерфейса set / get, поэтому я попытался добавить одно подобное:

void setVarA ( _structName *ptr, int valueA )
{
  ptr->varA = valueA;
}

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

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

varA - Адрес: 10 - Значение: 1

ptrA - Адрес: 22 - Значение: 10

Но в этом примере у меня есть только

myTest - Адрес: xy - Значение: ??

У меня проблемы с пониманием, где находится "физическое" представление соответствующего указателя myTest?

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

Может кто-нибудь объяснить мне, является ли этот метод действительно целесообразным для малых и средних встроенных проектов? с 1-2 разработчиками, работающими с кодом? В настоящее время кажется, что все эти методы указателя интерфейса требуют больше усилий, чем просто объявление структуры в моем заголовочном файле.

Заранее спасибо

Ответы [ 3 ]

4 голосов
/ 31 января 2020

Моя главная путаница заключается в том, где будут находиться данные объекта структуры?

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

как я могу добиться этого в моем простом примере?

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

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

Это способ заставить модули быть независимы друг от друга. Иногда он используется, чтобы скрыть реализации для клиентов или чтобы гарантировать стабильность ABI.

Но да, для внутреннего использования это обычно бремя (и мешает оптимизации, так как все становится черным ящиком для компилятора кроме случаев использования LTO et c.). Подход syntacti c, такой как public / private, в других языках, таких как C ++, намного лучше для этого.

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

Может кто-нибудь объяснить мне, является ли этот метод действительно целесообразным для небольших и средних встроенных проектов с 1-2 разработчики, работающие с кодом?

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

2 голосов
/ 31 января 2020

Как вы поняли, при использовании непрозрачного типа, такого как этот, основной исходный файл не может получить доступ к членам структуры, и на самом деле не знает, насколько велика структура. Из-за этого вам нужны не только функции доступа для чтения / записи полей структуры, но и функция для выделения памяти для структуры, поскольку только источник библиотеки знает определение и размер структуры.

Таким образом, ваш заголовочный файл будет содержать следующее:

typedef struct _structName structType_t;

structType_t *init();
void setVarA(structType_t *ptr, int valueA );
int getVarA(structType_t *ptr);
void cleanup(structType_t *ptr);

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

#include "struct_test.h"

struct _structName
{
    int varA;
    int varB;
    char varC;
};

structType_t *init()
{
    return malloc(sizeof(structType_t ));
}

void setVarA(structType_t *ptr, int valueA )
{
    ptr->varA = valueA;
}

int getVarA(structType_t *ptr)
{
    return ptr->varA;
}

void cleanup(structType_t *ptr)
{
    free(ptr);
}

Обратите внимание, что вам нужно определить typedef только один раз. Это и определяет псевдоним типа, и forward объявляет структуру. Затем в исходном файле появляется фактическое определение структуры без typedef.

Функция init используется вызывающей стороной для выделения пространства для структуры и возврата указателя на нее. Этот указатель затем может быть передан в функции получения / установки.

Так что теперь ваш основной код может использовать этот интерфейс следующим образом:

#include "struct_test.h"

int main()
{
    structType_t *s = init();
    setVarA(s, 5);
    printf(s->a=%d\n", getVarA(s));
    cleanup(s);l
}
1 голос
/ 31 января 2020

В сообщении упоминается, что доступ обрабатывается с помощью методов интерфейса set / get, поэтому я попытался добавить одно подобное:

void setVarA ( _structName *ptr, int valueA )
{
  ptr->varA = valueA;
}

Но это также не не работает, потому что теперь он говорит мне, что _structName неизвестно ...

Тип не _structName, а struct _structName или (как определено) structType_t.

И мой еще больший вопрос остается в том, где находится объект моей структуры в памяти.

При использовании этого метода был бы метод, который возвращает адрес такого непрозрачного объекта. Это может быть статически или динамически распределено. Конечно, также должен быть метод для освобождения объекта.

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

Я согласен с вами.

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