C 确保仅向可变函数传递指针

C 确保仅向可变函数传递指针,c,function,pointers,preprocessor,variadic,C,Function,Pointers,Preprocessor,Variadic,我构建了一个类似printf的变量函数,用于在定制CPU上记录日志 为了确保没有内存故障,我编写了这个函数,以便它只接受指针并处理它们。预处理器总是在va列表的末尾添加一个空指针,因此每当函数重新指定一个零指针时,它都会返回。通过这种方式,我可以确定我没有阅读列表。 我这样做是为了防止程序的错误处理,它应该是一个安全的功能。用户在va列表中输入的任何内容都不应以崩溃告终 我现在唯一的问题是确保用户只能传递变量函数的指针,没有其他(除了前两个不属于va列表的参数)我的函数声明如下所示: void

我构建了一个类似printf的变量函数,用于在定制CPU上记录日志

为了确保没有内存故障,我编写了这个函数,以便它只接受指针并处理它们。预处理器总是在va列表的末尾添加一个空指针,因此每当函数重新指定一个零指针时,它都会返回。通过这种方式,我可以确定我没有阅读列表。 我这样做是为了防止程序的错误处理,它应该是一个安全的功能。用户在va列表中输入的任何内容都不应以崩溃告终

我现在唯一的问题是确保用户只能传递变量函数的指针,没有其他(除了前两个不属于va列表的参数)我的函数声明如下所示:

void LogNew( UINT LogClass, char* pMsg, ... );
void put(const char **s)
{
    while (*s) {
        fputs(*s++, stdout);
    }

    fputs("\n", stdout);
}

#define put(...) put((const char *[]){__VA_ARGS__, NULL})
put("Couldn't open \"", fn, "\".");
put("My name is ", (rand() % 2) ? "Igor" : "Tamara", ".");
put("");
以及无效终止:

#define LogNew(...) LogNew(__VA_ARGS__, 0)
是否有一种方法,可以通过预处理器,确保没有人使用按值调用而不是按引用调用


提前谢谢你

可以将每个参数包装在表达式中,以确保只允许使用指针(同时保留其值)。例如,可以将参数
elem
转换为:

(true ? (elem) : (void *) 0)
三元条件确保其第二个和第三个参数兼容。注意,这仍然允许文字零作为参数;您可以在运行时检测到(相应的参数将是空指针);此外,对于许多实现,这只会生成警告,而不是硬错误

另一个选项是对参数应用
&*
运算符。这里的问题是,这对
void
指针不起作用,但a可以帮助:

&*(_Generic((elem), void *: ((char*) elem), const void *: ((char const*) elem), default: (elem)))
包装每个参数是有帮助的,但是有一些宏编程库可以提供帮助。例如,使用以下命令可以编写:

#include <boost/preprocessor.hpp>

void LogNew(unsigned LogClass, char* pMsg, ... ) {}
#define LOG_NEW_ELEMENT(r,data,elem) \
    , &*(_Generic((elem), void *: ((char*) elem), const void *: ((char const*) elem), default: (elem)))
#define LOG_NEW_IMPL(LogClass,Args) \
    LogNew(LogClass, \
    BOOST_PP_SEQ_HEAD(Args) \
    BOOST_PP_SEQ_FOR_EACH(LOG_NEW_ELEMENT, _, BOOST_PP_SEQ_TAIL(Args)), \
    ((void*) 0))
#define LogNew(LogClass,...) \
    LOG_NEW_IMPL(LogClass, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))

可以将每个参数包装在表达式中,以确保只允许使用指针(同时保留其值)。例如,可以将参数
elem
转换为:

(true ? (elem) : (void *) 0)
三元条件确保其第二个和第三个参数兼容。注意,这仍然允许文字零作为参数;您可以在运行时检测到(相应的参数将是空指针);此外,对于许多实现,这只会生成警告,而不是硬错误

另一个选项是对参数应用
&*
运算符。这里的问题是,这对
void
指针不起作用,但a可以帮助:

&*(_Generic((elem), void *: ((char*) elem), const void *: ((char const*) elem), default: (elem)))
包装每个参数是有帮助的,但是有一些宏编程库可以提供帮助。例如,使用以下命令可以编写:

#include <boost/preprocessor.hpp>

void LogNew(unsigned LogClass, char* pMsg, ... ) {}
#define LOG_NEW_ELEMENT(r,data,elem) \
    , &*(_Generic((elem), void *: ((char*) elem), const void *: ((char const*) elem), default: (elem)))
#define LOG_NEW_IMPL(LogClass,Args) \
    LogNew(LogClass, \
    BOOST_PP_SEQ_HEAD(Args) \
    BOOST_PP_SEQ_FOR_EACH(LOG_NEW_ELEMENT, _, BOOST_PP_SEQ_TAIL(Args)), \
    ((void*) 0))
#define LogNew(LogClass,...) \
    LOG_NEW_IMPL(LogClass, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))

如果您想减少或防止错误格式的错误,在我看来,您最好使用
fprintf
。它是一个可变函数,因此不安全,但它是用C打印内容的方法,因此程序员熟悉它及其缺点。如果您的日志函数只是一个宏,例如:

enum {Fatal, Error, Warning, Info};

int maxlevel = Error;

#define logmsg(L, ...) if (L > maxlevel); else {        \
        if (L == Fatal) fprintf(stderr, "Fatal: ");     \
        if (L == Error) fprintf(stderr, "Error: ");     \
                                                        \
        fprintf(stderr, __VA_ARGS__);                   \
        fprintf(stderr, "\n");                          \
                                                        \
        if (L == Fatal) exit(1);                        \
    }                                                   \
您可以从静态分析中获益,它将警告您格式/类型不匹配。在clang/gcc中,
-Wformat
,它是
-Wall
的一部分,将执行此操作。在Visual Studio中,可以使用
/analyze
。(当日志记录被禁止时,您不会生成va列表。)

如果您滚动自己的变量参数函数并调用
vfprintf
,则可以在Visual Studio中使用clang/gcc
format
属性或
\u Printf\u format\u string\u
操作

也就是说,如果所有变量参数的类型相同,则可以使用数组。如果编译器支持C99,则可以使用宏将参数列表的变量部分转换为数组

假设所有打印的参数都应该是
const char*
。(我知道这不是你想要的,但请耐心听我说。)然后你可以实现一个打印函数,它可以接受任意数量的C字符串,如下所示:

void LogNew( UINT LogClass, char* pMsg, ... );
void put(const char **s)
{
    while (*s) {
        fputs(*s++, stdout);
    }

    fputs("\n", stdout);
}

#define put(...) put((const char *[]){__VA_ARGS__, NULL})
put("Couldn't open \"", fn, "\".");
put("My name is ", (rand() % 2) ? "Igor" : "Tamara", ".");
put("");
然后像这样使用它:

void LogNew( UINT LogClass, char* pMsg, ... );
void put(const char **s)
{
    while (*s) {
        fputs(*s++, stdout);
    }

    fputs("\n", stdout);
}

#define put(...) put((const char *[]){__VA_ARGS__, NULL})
put("Couldn't open \"", fn, "\".");
put("My name is ", (rand() % 2) ? "Igor" : "Tamara", ".");
put("");
您可以访问
const void*
。我将代码隐藏在Ideone链接后面,因为使用
void
将丢失参数的类型信息。您的代码可能不会严重崩溃,因为指针应该指向某个地方,但当您使用错误的格式时,仍然会调用未定义的行为并获得垃圾输出


(在对第一个答案的评论中,Lundin指出将整数类型转换为指针是合法的。这不是所示答案的缺陷,而是语言的缺陷。你也可以说
put(54)
,只得到警告。)

如果你想减少或防止错误格式的错误,在我看来,您最好使用
fprintf
。它是一个可变函数,因此不安全,但它是用C打印内容的方法,因此程序员熟悉它及其缺点。如果您的日志函数只是一个宏,例如:

enum {Fatal, Error, Warning, Info};

int maxlevel = Error;

#define logmsg(L, ...) if (L > maxlevel); else {        \
        if (L == Fatal) fprintf(stderr, "Fatal: ");     \
        if (L == Error) fprintf(stderr, "Error: ");     \
                                                        \
        fprintf(stderr, __VA_ARGS__);                   \
        fprintf(stderr, "\n");                          \
                                                        \
        if (L == Fatal) exit(1);                        \
    }                                                   \
您可以从静态分析中获益,它将警告您格式/类型不匹配。在clang/gcc中,
-Wformat
,它是
-Wall
的一部分,将执行此操作。在Visual Studio中,可以使用
/analyze
。(当日志记录被禁止时,您不会生成va列表。)

如果您滚动自己的变量参数函数并调用
vfprintf
,则可以在Visual Studio中使用clang/gcc
format
属性或
\u Printf\u format\u string\u
操作

也就是说,如果所有变量参数的类型相同,则可以使用数组。如果编译器支持C99,则可以使用宏将参数列表的变量部分转换为数组

假设所有打印的参数都应该是
const char*
。(我知道这不是你想要的,但请耐心听我说。)然后你可以实现一个打印函数,它可以接受任意数量的C字符串,如下所示:

void LogNew( UINT LogClass, char* pMsg, ... );
void put(const char **s)
{
    while (*s) {
        fputs(*s++, stdout);
    }

    fputs("\n", stdout);
}

#define put(...) put((const char *[]){__VA_ARGS__, NULL})
put("Couldn't open \"", fn, "\".");
put("My name is ", (rand() % 2) ? "Igor" : "Tamara", ".");
put("");
然后像这样使用它:

void LogNew( UINT LogClass, char* pMsg, ... );
void put(const char **s)
{
    while (*s) {
        fputs(*s++, stdout);
    }

    fputs("\n", stdout);
}

#define put(...) put((const char *[]){__VA_ARGS__, NULL})
put("Couldn't open \"", fn, "\".");
put("My name is ", (rand() % 2) ? "Igor" : "Tamara", ".");
put("");
您可以访问
const void*
。我把代码隐藏在一个Ideone链接后面,