C中的宏指令,我的代码示例不起作用

pfu*_*pfu 2 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);
}
Run Code Online (Sandbox Code Playgroud)

我不明白,如何使用#运算符的指令.我想使用scanf与打印错误等了好几次,但"'"#%d"''"&a"'"被我认为完全错误的.有没有办法让它运行?我认为宏是最好的解决方案吗?

Jon*_*ler 6

您应该只将参数字符串化为宏,并且它们必须位于宏的替换文本中的字符串或字符常量之外.因此你可能应该使用:

#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);
}
Run Code Online (Sandbox Code Playgroud)

有很多变化.这个do { ... } while (0)习惯用法可以防止你在以下情况下遇到编译错误:

if (i > 10)
    READIN(u, j);
else
    READIN(u, k);
Run Code Online (Sandbox Code Playgroud)

使用你的宏,你会得到一种unexpected keyword 'else'消息,因为第READIN()一个之后的分号在嵌入后是一个空语句if,因此else不能属于可见ifif宏内部.

类型stack_sizeunsigned int; 因此,正确的格式说明符是u(d用于签名int).

并且,最重要的是,a宏中的参数是正确的字符串化(并且相邻字符串文字的字符串连接 - C89的一个非常有用的功能! - 为您处理其余部分.并且b宏中的参数不嵌入字符串中无论是.

完成错误报告stderr(用于报告错误的标准流),并且消息以换行符结束,以便实际显示.我没有替换,但如果宏将在外面return EXIT_FAILURE;使用exit(EXIT_FAILURE);,那可能是一个明智的选择main().这假设'终止错误'是首先适当的行为.它通常不适用于交互式程序,但修复它有点困难.

我也无视我对使用的保留意见scanf(); 我通常会避免这样做,因为我发现错误恢复太难了.我只用C编程了大约28年,我仍然觉得scanf()很难控制,所以我基本上从不使用它.我通常使用fgets()sscanf()不是.在其他优点中,我可以报告造成麻烦的字符串; 当scanf()可能已经吞噬了一部分时,这很难做到.


我在scanf()这里的想法是,只读正数而不是字母.我的整体代码确实创建了一个用户输入的堆栈,类型应该只是正数,否则就是错误.[...]我只想知道是否有更好的解决方案禁止用户输入除正数之外的其他内容?

我刚刚尝试了上面的代码(使用#include <stdlib.h>#include <stdio.h>添加)并输入-2并被告知4294967294,这不是我想要的(%u格式不拒绝-2,至少在MacOS X 10.7.2上).所以,我会去fgets()strtoul(),最有可能的.然而,准确地检测所有可能的问题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);
}
Run Code Online (Sandbox Code Playgroud)

规范strtoul()在ISO/IEC 9899:1999§7.20.1.4中给出:

1[...]

unsigned long int strtoul(const char * restrict nptr,
char ** restrict endptr, int base);

[...]

2[...]首先,他们将输入字符串分解为三个部分:一个初始的,可能是空的白色空格字符序列(由isspace函数指定),一个类似于整数表示的整数的主题序列由base的值,以及一个或多个无法识别的字符的最终字符串,包括输入字符串的终止空字符.然后,他们尝试将主题序列转换为整数,并返回结果.

3[...]

4主题序列被定义为输入字符串的最长初始子序列,从第一个非空白字符开始,即预期形式.如果输入字符串为空或完全由空格组成,或者第一个非空白字符不是符号或允许的字母或数字,则主题序列不包含任何字符.

5如果主题序列具有预期形式且base的值为零,则根据6.4.4.1的规则将以第一个数字开头的字符序列解释为整数常量.如果主题序列具有预期形式并且base的值在2和36之间,则将其用作转换的基础,将其值归于每个字母,如上所述.如果主题序列以减号开头,则转换产生的值将被否定(在返回类型中).如果endptr不是空指针,则指向最终字符串的指针存储在endptr指向的对象中.

6[...]

7如果主题序列为空或者没有预期的形式,则不进行转换; 如果不是空指针,则值nptr存储在指向的对象中.endptrendptr

返回

8的strtol,strtoll,strtoul,和strtoull函数返回转换后的值,如果有的话.如果无法执行转换,则返回零.如果正确的值超出可表示值的范围,则返回LONG_MIN,LONG_MAX,LLONG_MIN,LLONG_MAX,ULONG_MAX或ULLONG_MAX(根据值的返回类型和符号,如果有),并且宏ERANGE的值为存储在errno中.

我得到的错误来自64位编译,其中-2转换为64位无符号长整数,超出了32位可接受的范围unsigned int(失败条件是u > UINT_MAX).当我以32位模式(so sizeof(unsigned int) == sizeof(unsigned long))重新编译时,-2再次接受该值,再次解释为4294967294.所以,即使这还不够精细......你可能不得不手动跳过前导空白并拒绝负号(也许是一个正号;你也需要#include <ctype.h>):

        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);
        }
Run Code Online (Sandbox Code Playgroud)

正如我所说,整个过程非常重要.

(再次看一下,我不确定该u == 0 && errno != 0条款是否会捕获任何错误......也许不是因为eos == buffer(或eos == bos)条件捕获了根本没有任何转换的情况.)

  • 特别感谢最后一段.我_knew_我出于某种原因不喜欢`scanf(3)`但是我无法用简单的理由说明原因. (2认同)