Статическая библиотека; Неиспользуемые символы; Определения в заголовке

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

Все мои классы имеют свои встроенные определения методов в заголовочном файле.

printer.h выглядит примерно так ….

class Printer {
public:
Printer (const PrintCapable *printCapable)
: m_printCapable(printCapable) {}

void put_char (const char c) { ... }

#ifdef ENABLE_PRINT_FLOAT
void put_float (const float f) { ... }
#endif

void printf (const char fmt[], ...) {
// Stuffs...

#ifdef ENABLE_PRINT_FLOAT
// Handle floating point support
#endif
}

private:
const PrintCapable *m_printCapable;
}

// Make it very easy for the user of this library to print by defining an instance for them
extern Printer out;

Теперь я понимаю, что это должно работать отлично.

printer.cpp это красиво и просто:

#include <printer.h>
#include <uart/simplexuart.h>

const SimplexUART _g_simplexUart;
const Printer     out(&_g_simplexUart);

Ненужный код раздувается:
Если я скомпилирую свою библиотеку с и проектирую без ENABLE_PRINT_FLOAT определяется, то размер кода составляет 9 216 кБ.

Нужный код наворотов:
Если я скомпилирую библиотеку и проект с ENABLE_PRINT_FLOAT, размер кода составляет 9,348 кБ.

Нужный код бло …. ой, подождите, это не раздутый
Если я скомпилирую проект с библиотекой без ENABLE_PRINT_FLOAT, Я мог бы ожидать чтобы увидеть то же, что и выше. Но нет … вместо этого у меня есть размер кода 7,092 КБ и программа, которая не выполняется правильно.

Минимальный размер:
Если я компилирую оба компилируются без ENABLE_PRINT_FLOATтогда размер кода составляет всего 6 960 кБ.

Как я могу достичь своей цели небольшого размера кода, гибких классов и простого в использовании?

Система сборки CMake. Полный источник проекта Вот.

Основной файл это красиво и просто:

#include <printer.h>

void main () {
int i = 0;

while (1) {
out.printf("Hello world! %u %05.2f\n", i, i / 10.0);
++i;
delay(250); // 1/4 second delay
}
}

0

Решение

Если у вас есть другое определение inline функции в разных единицах перевода у вас есть неопределенное поведение. Так как ваш printf() определение меняется с настройкой ENABLE_PRINT_FLOAT макрос вы просто видите этот эффект.

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

Вы мог сделайте поддержку плавающей запятой параметром шаблона вашего printf() функция: функция будет вызвана с помощью

out.printf<false>("%d\n", i);
out.printf<true>("%f", f);

Реализация printf() делегировал бы подходящим внутренним функциям (чтобы определения слияния компилятора были идентичными) с отключенной поддержкой плавающей запятой для false случай: он может ничего не делать, провалиться или утверждать.

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

Если для вашей библиотеки есть возможность использовать C ++ 11, вы можете использовать шаблон с переменным числом аргументов, чтобы справиться с ситуацией: отдельный форматер будет реализован как отдельные функции, которые отправляются внутрь printf(): этого пути нет printf() функция, которая должна обрабатывать все форматирование. Вместо этого будут извлечены только необходимые средства форматирования типов. Реализация может выглядеть примерно так:

inline char const* format(char const* fmt, int value) {
// find format specifier and format value accordingly
// then adjust fmt to point right after the processed format specifier
return fmt;
}
inline char const* format(char const* fmt, double value) {
// like the other but different
}
// othe formatters

inline int printf(char const* fmt) { return 0; }
template <typename A, typename... T>
inline int printf(char const* fmt, A&& arg, T&& args) {
fmt = format(fmt, std::forward<A>(arg));
return 1 + printf(fmt, std::forward<T>(args));
)

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

2

Другие решения


По вопросам рекламы [email protected]