C中的宏指令,我的代码示例没有';行不通

C中的宏指令,我的代码示例没有';行不通,c,macros,c-preprocessor,C,Macros,C Preprocessor,我希望获得以下代码块: #define READIN(a, b) if(scanf('"#%d"', '"&a"') != 1) { printf("ERROR"); return EXIT_FAILURE; } int main(void) { unsigned int stack_size; printf("Type in size: "); READIN(d, stack_size); } 我不明白,如何将指令与#操作符一起使用。我想多次使用带有打印错误

我希望获得以下代码块:

#define READIN(a, b) if(scanf('"#%d"', '"&a"') != 1) { printf("ERROR"); return EXIT_FAILURE; }

int main(void)
{
    unsigned int stack_size;
    printf("Type in size: ");
    READIN(d, stack_size);
}

我不明白,如何将指令与
#
操作符一起使用。我想多次使用带有打印错误等的
scanf
,但是
“#%d”
“&a”
是完全错误的。有没有办法让它运转起来?我认为宏是否是最好的解决方案?

如果您不正确地封装了宏参数,它应该如下所示:

#define READIN(a, b) if(scanf("%"#a, &b) != 1) { printf("ERROR"); return EXIT_FAILURE; }
您使用的stringify运算符也不正确,它必须直接作为参数名称的前缀

简而言之,使用
“%”a
,而不是
“%”d“
&b
,而不是
”&a“

作为旁注,对于像这样的长宏,它有助于使用
\
使它们多行,这使它们可读:

#define READIN(a, b) \
if(scanf("%"#a, &b) != 1) \
{ \
    printf("ERROR"); \
    return EXIT_FAILURE; \
}
当做类似的事情时,最好使用一个函数,沿着这条线的东西应该可以工作:

inline int readIn(char* szFormat, void* pDst)
{
    if(scanf(szFormat,pDst) != 1)
    {
        puts("Error");
        return 0;
    }

    return 1;
}
调用它就像这样:

if(!readIn("%d",&stack_size))
    return EXIT_FAILURE;
scanf(3)
const char*
作为第一个参数。您正在传递的是
“…”
,它不是C“字符串”。C字符串是用双引号写的。
单引号用于单个字符:
'a'
'\n'

return
语句放在C预处理器宏中通常被认为是非常糟糕的形式。我以前见过
goto error;
在预处理器宏中编码,用于在将格式化数据存储到文件或内核接口并从中读取数据时重复错误处理代码,但这绝对是例外情况ces。你会讨厌在六个月内调试这个。相信我。不要在C预处理器宏中隐藏
转到
返回
中断
继续
如果
可以,只要它完全包含在宏中

另外,请养成这样写
printf(3)
语句的习惯:

printf("%s", "ERROR");
非常容易编写。您的代码现在不包含任何此类漏洞,但请相信我,在将来的某个时候,这些字符串不可避免地会被修改,以包含一些用户提供的内容,现在放入显式格式字符串将有助于防止将来出现这些漏洞。如果您看到这个

是的

最后,测试没有完全正确完成;请尝试以下方法:

#define READIN(A, B) do { if (scanf("%" #A, B) != 1) { \
        /* error handling */ \
    } else { \
        /* success case */ \
    } } while(0)

编辑:我觉得我应该重新迭代:改用函数。你可以更好地进行类型检查,在出现问题时可以更好地进行回溯,而且使用起来更容易。函数很好。

你应该只对宏的参数进行字符串化,它们必须在替换文本o中的字符串或字符常量之外f宏。因此,您可能应该使用:

#define READIN(a, b) do { if (scanf("%" #a, &b) != 1) \
                          { fprintf(stderr, "ERROR\n"); return EXIT_FAILURE; } \
                     } while (0)

int main(void)
{
    unsigned int stack_size;
    printf("Type in size: ");
    READIN(u, stack_size);
    printf("You entered %u\n", stack_size);
    return(0);
}
有许多更改。
do{…}while(0)
习惯用法可防止在以下情况下出现编译错误:

if (i > 10)
    READIN(u, j);
else
    READIN(u, k);
使用宏时,您会得到一个
意外关键字'else'
类型的消息,因为第一个
READIN()
后面的分号在嵌入的
if
之后是一个空语句,因此
else
不能属于宏中可见的
if
if

stack\u size
的类型是
unsigned int
;因此,正确的格式说明符是
u
d
表示有符号的
int

而且,最重要的是,宏中的参数
a
被正确地字符串化(相邻字符串文字的字符串串联-C89的一个非常有用的功能!-为您解决了其余问题。宏中的参数
b
也没有嵌入到字符串中

错误报告是在
stderr
(用于报告错误的标准流)上完成的,消息以换行符结尾,因此它实际上会出现。我没有将
return EXIT\u FAILURE;
替换为
EXIT(EXIT\u FAILURE);
,但是如果宏将在
main()之外使用,这可能是一个明智的选择
。首先,假设“出错时终止”是合适的行为。通常情况下,这不适用于交互式程序,但修复它要困难一些

我也完全忽略了我对使用
scanf()
的保留意见;我通常避免这样做,因为我发现错误恢复太难了。我用C编程才28年,但我仍然觉得
scanf()
太难控制,所以我基本上从未使用过它。我通常使用
fgets()
sscanf()
取而代之。除其他优点外,我可以报告导致问题的字符串;当
scanf()
可能占用了一些字符串时,这很难做到


我对
scanf()
的想法是,只读取正数而不读取字母。我的总体代码确实创建了一个堆栈,用户在其中键入,并且类型应仅为正数,否则会出错。[…]我只想知道是否有更好的解决方案来禁止用户键入正数以外的内容

我刚刚尝试了上面的代码(添加了
#include
#include
),然后输入了
-2
,结果被告知4294967294,这不是我想要的(至少在MacOS X 10.7.2上,
%u
格式不会拒绝
-2
),所以我会选择
fgets()
strotul()
,最有可能。但是,准确地检测strtoul()的所有可能问题是一种微妙的练习

这是我提出的备选代码:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <string.h>

int main(void)
{
    unsigned int stack_size = 0;
    char buffer[4096];
    printf("Type in size: ");
    if (fgets(buffer, sizeof(buffer), stdin) == 0)
        printf("EOF or error detected\n");
    else
    {
        char *eos;
        unsigned long u;
        size_t len = strlen(buffer);
        if (len > 0)
            buffer[len - 1] = '\0';  // Zap newline (assuming there is one)
        errno = 0;
        u = strtoul(buffer, &eos, 10);
        if (eos == buffer ||
            (u == 0 && errno != 0) ||
            (u == ULONG_MAX && errno != 0) ||
            (u > UINT_MAX))
        {
            printf("Oops: one of many problems occurred converting <<%s>> to unsigned integer\n", buffer);
        }
        else
            stack_size = u;
        printf("You entered %u\n", stack_size);
    }
    return(0);
}
正如我所说,整个过程相当繁琐

(再看一遍,我不确定
u==0&&errno!=0
子句是否会捕获任何错误……可能不是因为
eos==buffer
(或者
        char *bos = buffer;
        while (isspace(*bos))
            bos++;
        if (!isdigit(*bos))
            ...error - not a digit...
        char *eos;
        unsigned long u;
        size_t len = strlen(bos);
        if (len > 0)
            bos[len - 1] = '\0';  // Zap newline (assuming there is one)
        errno = 0;
        u = strtoul(bos, &eos, 10);
        if (eos == bos ||
            (u == 0 && errno != 0) ||
            (u == ULONG_MAX && errno != 0) ||
            (u > UINT_MAX))
        {
            printf("Oops: one of many problems occurred converting <<%s>> to unsigned integer\n", buffer);
        }