C++ / [Из песочницы] Макросы с переменным числом параметров

в 23:23, , рубрики: c plus plus, c++, debug, отладка, метки: , , ,

Недавно пришлось мне разбираться с одним Open Source проектом. Нужно было разобраться с одной ошибкой. Ошибка была плавающей и проявлялась исключительно на стенде, после получаса раб. Да и то не всегда. Поэтому было принято решение логировать определенные участки кода.
Поэтому была написана простая функция:
void dbg(const char * AMsg);
которая записывала строку в лог. Вскоре оказалось, что такой функции недостаточно и она была переписана в таком виде:
void dbg(const char * AFmt, ...);
т.е. теперь она при помощи функции vfprintf() записывала в файл форматированную строку. По мере роста числа вызовов, захотелось писать в файл еще два параметра, а именно __LINE__ и __FILE__. Передавать эти два параметра при каждом вызове функции не хотелось, поэтому было принято решение написать макрос-обертку над функцией dbg(), который бы принимал переменное число параметров, вызывал исходную функцию и передавал бы ей двумя первыми параметрами имя файла и номер строки.
Погуглив, я нашел довольно красивое решение — описать класс и перегрузить в нем оператор (). Я набросал тестовый проект, протестировал новый макрос и измененную функцию и остался доволен результатом. После этого подключил файлы в исходный проект, h-файл заинклюдил в «StdAfx.h» и нажал Build. И тут меня настигло глубокое разочарование. Оказалась, что часть проекта написана на чистом Си, который имел в виду мои классы.
Погуглив еще, я нашел такое решение. Но увы, я использовал VC6…
И тогда меня посетила идея — а, что если макрос будет заменяться функцией, которая будет принимать параметры __FILE__ и __LINE__, сохранять их где нибудь и возвращать указатель на оригинальную функцию dbg(). А эта функция будет считывать сохраненные параметры и записывать их в файл. Параметры было решено сохранять в глобальные переменные. А сами переменные, дабы не нарваться на грабли при многопоточной работе, объявить как __declspec(thread). B вот, что получилось в итоге:
h-файл
// File debug_info.h
#ifndef __DEBUG_INFO_H
#define __DEBUG_INFO_H

// Объявление типа указателя на функцию, записывающей лог
typedef void (* DbgFuncType)(const char * AFmt, ...);

// Функция, сохраняющая параметры __FILE__ и __LINE__, и возвращающая указатель на функцию логирования
#ifdef __cplusplus
extern "C"
#endif
DbgFuncType DbgFuncRet(const char* AFile, int ALine);

// Собственно сам макрос
#define dbg DbgFuncRet(__FILE__, __LINE__)

#endif

cpp-файл
// File debug_info.h
#include "StdAfx.h"
#include
#include

__declspec(thread) char __File__[MAX_PATH];
__declspec(thread) int __Line__;

void DbgFunc(const char * AFmt, ...) {
FILE * log;
if (fopen_s(&log, log_name, "a"))
return;
va_list args;
va_start(args, AFmt);
vfprintf(log, AFmt, args);
fprintf(log, "File: "%s", Line: %dn", __File__, __Line__);
fclose(log);
va_end(args);
}

DbgFuncType DbgFuncRet(const char* AFile, int ALine) {
strcpy_s(__File__, MAX_PATH, AFile);
__Line__ = ALine;
return &DbgFunc;
}

Вот и все. Единственное, на чем хотелось бы остановиться, это объявление функции DbgFuncRet()
#ifdef __cplusplus
extern "C"
#endif
DbgFuncType DbgFuncRet(const char* AFile, int ALine);

Т.к. заголовок подключается и в c и в cpp файлы, то транслятор имен будет применять разные декорации для функции. А такое объявление говорит транслятору всегда использовать декорацию C.


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js