Как я могу скрыть содержимое определения препроцессора C, доступного пользователю, в непользовательском коде? - PullRequest
1 голос
/ 07 мая 2020

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

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

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

#define MY_ELEMENT_SIZE (sizeof(component_1_type) + sizeof(component_2_type))

Однако component_x_type предназначен для скрытия от пользователя, поэтому это определение не может go в publi c заголовок с прототипами для интерфейсных функций.

Наша следующая идея заключалась в том, чтобы иметь переменную const в источнике:

const int MY_ELEMENT_SIZE = sizeof(component_1_type) + sizeof(component_2_type);

и объявление extern в publi c header:

extern const int MY_ELEMENT_SIZE;

Но поскольку это C89 и у нас есть педантизм, MISRA и другие требования, которые необходимо выполнить, мы не можем использовать массивы переменной длины. В «пользовательском» исходном файле, чтобы получить необработанный буфер из 50 элементов, мы пишем:

char rawBuffer[50 * MY_ELEMENT_SIZE] = {0u};

Использование метода extern const... приводит к ошибке компиляции:

error: variably modified ‘rawBuffer’ at file scope

Это не было полностью неожиданным, но разочаровывает то, что sizeof(any_type) действительно постоянен и известен во время компиляции.

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

Большое, большое спасибо.

Ответы [ 3 ]

0 голосов
/ 07 мая 2020

В дополнение к другим ответам, это довольно примитивное предложение. Но это легко понять.

Поскольку, вероятно, вы не будете публиковать sh файлы заголовков слишком часто для своих клиентов и, следовательно, не будете изменять размеры типов, вы можете использовать (вручную или автоматически ) вычисленное определение:

#define OUR_LIB_TYPE_X_SIZE 23

В своих частных источниках вы можете затем проверить правильность этого предположения, например,

typedef char assert_type_x_has_size[2 * (sizeof (TypeX) == OUR_LIB_TYPE_X_SIZE) - 1];

Это приведет к ошибке на любом достойном компиляторе при неравных размерах, потому что размер массива будет -1 и недопустим. При равных размерах размер массива равен 1 и разрешен.

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

0 голосов
/ 07 мая 2020

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

Я решил это точно так, как KamilCuk показал в комментариях: дать вызывающему необработанное «magi c число» через #define в файле .h, затем stati c assert внутри. c реализации, проверяющей соответствие определения размеру объекта.

Если это недостаточно элегантно, то, возможно, вы могли бы рассмотреть передача распределения размера API времени выполнения из «класса»:

uint8_t* component1_get_raw_buffer (size_t n);

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

uint8_t* raw_buffer;
raw_buffer = component1_get_raw_buffer(50);

Это включает в себя некоторые внутренние уловки, отслеживающие, сколько памяти выделено (и обработка ошибок - возможно, возврат NULL в случае ошибки). Вы должны зарезервировать фиксированный максимальный размер для внутреннего буфера c, чтобы охватить наихудший сценарий использования.

(Необязательно: const квалифицировать возвращаемый указатель, если пользователь не должен изменить данные)

Преимущества: лучший объектно-ориентированный дизайн, отсутствие выделения кучи, соответствие MISRA- C. Недостатками являются накладные расходы на вызов функций во время инициализации и необходимость заранее выделить «достаточно» памяти.

Кроме того, этот метод небезопасен в многопоточной среде, но обычно это не проблема во встроенных системы.

0 голосов
/ 07 мая 2020

В моем коде C89

Сейчас 2020 год. Обсудите со своим менеджером или клиентом возможность использования менее устаревшего C стандартного . На практике большую часть написанного вручную кода C89 можно разумно перенести на C11, и вы можете использовать, купить или разработать инструменты рефакторинга или услуги, которые помогут вам в этом (например, ваш G CC плагин ). Напомните своему менеджеру или клиенту, что технический долг требует больших затрат (вероятно, десятки тысяч долларов США или евро). Обратите внимание, что старые компиляторы C89 на практике оптимизируют гораздо меньше, чем недавние, и что большинство младших разработчиков (ваши будущие коллеги) даже не знакомы с C89 (поэтому им потребуется какое-то обучение, которое стоит немало lot).

Как я могу скрыть содержимое определения препроцессора C в непользовательском коде?

Насколько я знаю, вы не может (теоретически). Проверьте, прочитав стандарт C11 n1570 . Прочтите также документацию GNU cpp, затем G CC (или вашего C компилятора).

у нас есть педантизм и MISRA и другие требования для выполнения

Имейте в виду, что эти требования требуют затрат. Напомните об этих затратах своему клиенту или менеджеру.


(о сокрытии содержимого препроцессора C, доступного пользователю #define)

Однако, на практике может быть сгенерирован код C (например, внутри некоторого внутреннего файла заголовка #include -d в вашей единице перевода ), и это обычная практика (посмотрите GNU bison или SWIG для хорошо известного примера генератора кода C, а также рассмотрите возможность использования GNU m4 или gpp или вашего собственного Guile или Python скрипт, или ваша собственная программа C или C ++, выдающая код C). Вам просто нужно настроить свою автоматизацию сборки инфраструктуру (например, написать свой Makefile) для такого случая.

Если у вас есть скрипт или утилита, генерирующая такие вещи, как #define MACRO_7oa7eIzzcxv03Tm (где MACRO_7oa7eIzzcxv03Tm - какой-то псевдослучайный или искаженный идентификатор идентификатор), то вероятность случайного столкновения с клиентским кодом довольно мала. Программист-человек вряд ли придумает такие идентификаторы, и при достаточной осторожности скрипт, генерирующий C, обычно не будет выдавать идентификаторы, конфликтующие с ним. См. Также этот ответ.

Возможно, ваш клиент или менеджер разрешит вам использовать (на вашем рабочем столе) какой-нибудь генератор такого "случайного" идентификатора. AFAIK, они совместимы с MISRA (но мой стандарт MISRA находится в офисе, и я - май 2020 г.- в настоящее время Covid19 находится дома, недалеко от Парижа, Франция).

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

Вы можете (с одобрения менеджера и клиента) рассмотреть возможность использования struct -s с гибкими элементами массива или использовать массивы измерения 0 или 1 в качестве последний член вашего struct -с. IIR C, что было обычной практикой в ​​SunOS3.2

Рассмотрите также возможность использования таких инструментов, как Frama- C, Clang stati c analyzer или -в конце 2020 года мой Bismon в сочетании с недавним G CC. Подумайте о субподряде на проверку кода вашего исходного кода.

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