Pull to refresh

Макросы с переменным числом параметров

Reading time 2 min
Views 17K
Недавно пришлось мне разбираться с одним 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 <stdio.h>
#include <stdarg.h>

__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: %d\n", __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.
Tags:
Hubs:
+9
Comments 26
Comments Comments 26

Articles