【问题标题】:how to use c macro with three lines of statement in c如何在c中使用带有三行语句的c宏
【发布时间】:2015-03-20 13:20:32
【问题描述】:

我想了解如何在我的代码中有效地使用 C 宏。有人可以就下面提到的代码指导我吗?

#include<stdio.h>
#include<string.h>

void write_strongswan_conf(char buffer[1000],int buffer_size)
{
   FILE *fp;
   char delimiter[]="\n";
   fp = fopen( "strongswan.txt" , "a" );
   strcat(buffer,"\n");
   fwrite(buffer, 1 ,buffer_size+1, fp );
   printf("Data Written:%s",buffer);
   fclose(fp);
}

int main ()
{
    char wr_buffer[1000];
    int  wr_buffer_size =1;

    strcpy(wr_buffer,"STRONG SWAN CONFIGURATION FILE");
    wr_buffer_size = strlen(wr_buffer);
    write_strongswan_conf(wr_buffer,wr_buffer_size);

    strcpy(wr_buffer,"THIS IS THE SECOND LINE");
    wr_buffer_size = strlen(wr_buffer);
    write_strongswan_conf(wr_buffer,wr_buffer_size);

   return(0);
}

这是一个将新字符串附加到文件末尾的小程序,我有大约 25 个字符串要添加到文件中。截至目前,我必须重复编写下面提到的三行来完成它。如果我的理解是正确的,我们将能够通过为这三行实现宏来减少 LOC。

有人可以指导我吗?

    strcpy(wr_buffer,"STRONG SWAN CONFIGURATION FILE");
    wr_buffer_size = strlen(wr_buffer);
    write_strongswan_conf(wr_buffer,wr_buffer_size);

【问题讨论】:

  • 或更简单的方法是将其放入具有正确参数的函数中。
  • 你想写一个多行宏?只需用“\”结束宏的每一行
  • 感谢您的响应。函数是一种方法,但我想了解宏的概念并使用它。我知道它会降低可读性,但我有兴趣了解宏。
  • 但是三行之间,字符串会有所不同。本例为“STRONG SWAN CONFIGURATION FILE”
  • 您必须将其作为字符串参数传递。

标签: c c-preprocessor


【解决方案1】:

这是我的镜头:

#define DO_SOMETHING(buf, str) \
    do { \
        const char *in = str; \
        char *out = buf; \
        const size_t len = strlen(in); \
        strcpy(out, in); \
        write_strongswan_conf(out, len);
    } while (0)

以这种方式编写宏的原因是什么?

  1. do {} while(0) 不是绝对必要的,但这是在条件内正确扩展多语句操作的唯一方法。例如,if (condition) DO_SOMETHING(...)。请参阅more elaborate answer 了解此内容。

  2. 创建临时变量的原因是宏参数每次出现在宏体中时都会被计算。例如,用PRINT_ME_TWICE(i++) 调用宏PRINT_ME_TWICE(intval) printf("%d\n", intval); printf("%d\n", intval); 将打印不同的值,因为intval 参数被计算了两次,因此执行了两次后增量运算符(++)。您只能通过创建临时变量、将参数分配给它们并且不再提及参数来保证不会发生求值问题。

注意:如果您不能保证输出缓冲区有足够的空间来保存结果字符串(加上 NULL 终止符),我会反对使用 strcpy

更新

正如@Bill Lynch 在 cmets 中提到的,如果您有与宏内部使用的名称相同的变量,上述版本可能仍然会给您带来麻烦。这被称为Variable Shadowing。任何好的编译器都应该能够发出警告。例如,GCC 会警告您甚至在没有 -Wall-Wextra 的情况下进行编译。目前,我们可以为宏做的最好的事情就是尝试通过在宏中为变量名添加前缀来最小化隐藏的风险。这可能是一个很好的做法,但它不是一个真正的解决方案。这就是它的样子:

#define DO_SOMETHING(buf, str) \
    do { \
        const char *DO_SOMETHING_in = str; \
        char *DO_SOMETHING_out = buf; \
        const size_t DO_SOMETHING_len = strlen(DO_SOMETHING_in); \
        strcpy(DO_SOMETHING_out, DO_SOMETHING_in); \
        write_strongswan_conf(DO_SOMETHING_out, DO_SOMETHING_len);
    } while (0)

一个好的建议是尽可能用适当的函数替换宏,避免所有晦涩的宏。

【讨论】:

  • 感谢您提供此服务。我正准备用这种技术更新我的帖子,但你打败了我! (+1)
  • @lurker:谢谢! :-) 不用担心,如果您更新答案,我也很乐意支持它!也许我应该首先编辑你的。没有争议。
  • 您的代码仍然会出现问题:gist.github.com/sharth/0eb6ebe7858de9ba49aa
  • @BillLynch:变量阴影?而且,您是否故意将参数顺序向后传递? (或他们的名字具有误导性)
  • @jweyrich:就个人而言,真正的解决方案是使用函数。如有必要,只需编写一个宏来包装对该函数的调用。
【解决方案2】:

这很简单:

#define FOO(buf, str) \
    strcpy(buf, str); \
    write_strongswan_conf(buf, strlen(buf));

注意缓冲区大小参数是多余的,所以这里不需要。

然后通过以下方式引用它:

int main ()
{
    char wr_buffer[1000];

    FOO(wr_buffer, "STRONG SWAN CONFIGURATION FILE");
    FOO(wr_buffer, "THIS IS THE SECOND LINE");

    return(0);
}

如果您确实需要宏中的本地参数,您可以使用宏定义一个块:

#define FOO(buf, str) \
    { \
        int bsize; \
        strcpy(buf, str); \
        bsize = strlen(buf); \
        write_strongswan_conf(buf, bsize); \
    }

编辑:感谢@jweyrich 和@BillLynch 的cmets,他们指出了potential pitfalls with the above method,并且最好在宏中呈现如下代码块:

#define FOO(buf, str) \
    do { \
        strcpy(out, str); \
        write_strongswan_conf(buf, strlen(str)); \
    } while (0)

块结构还有另一个好处,那就是如果你想在 C 中的复合语句中使用你的宏。如果你采用上面的第一个定义并这样做:

if ( some_check )
     FOO(wr_buffer, "STRONG SWAN CONFIGURATION FILE");

您不会得到预期的结果,因为if 仅适用于三个语句中的第一个。当然,一个好的解决方案是始终阻止您的陈述:

if ( some_check ) {
     FOO(wr_buffer, "STRONG SWAN CONFIGURATION FILE");
}

但您也可以在宏定义中使用模式do { ... } while (0),如上面第二个示例所示,这样也可以避免该问题。

请注意,有时在宏定义中为参数加上括号很重要。我没有在上面这样做,因为这不太可能是一个问题,但假设你有一个这样的宏:

#define MUL(X, Y)   (X * Y)

这看起来很无辜。让我们试试吧:

int a = 2;
int b = 3
int c;

c = MUL(a, b);

这会生成:

c = (a * b);

请注意,#defines 是相当字面的。包括括号。预处理器只是直接替换我定义的所有内容。如果我的表达更复杂:

c = MUL(a + 2, b + 3);

然后我得到:

c = (a + 2 * b + 3);

等等...这将给我一个等同于a + (2*b) + 3 的结果,这可能不是我想要的。所以我用括号更仔细地定义了宏:

#define MUL(X, Y)  ((X) * (Y))

现在上面的内容将扩展为:c = ((a + 2) * (b + 3));

顺便说一句,如果您在使用宏和/或其他 #defines 时遇到任何奇怪的行为,只需在您的 .c 文件上运行预处理器会很方便,这将扩展所有 #defines无需编译。输出一开始可能有点笨拙,但它对于识别复杂的宏问题很方便。您可以确切地看到它生成了什么。

在 Unix/Linux 中,您只需运行 cpp 命令:cpp myfile.c &gt; myfile.pre 或任何您想调用的文件。如果您使用的是 Visual Studio,则可能有一个用于生成预处理文件的选项。

郑重声明,如果是我,我可能会将其设为函数,而不是宏。它确实只节省了一小部分执行时间,使之类的宏。

与编码的各个方面一样:仔细考虑并仔细考虑您正在做的事情很重要。 :)

【讨论】:

  • 完成了这项工作!谢谢:)
  • 如果宏从不读取其值,则不需要 bsize 参数。它目前具有误导性。
  • @jweyrich 我不确定我是否理解您的评论。 bsize 参数用于write_strongswan_conf(buf, bsize)
  • @lurker:是的,但是当您执行bsize = strlen(buf); 时,您只需在宏中覆盖它,那么为什么要在第一个位置通过参数传递它呢?您完全忽略了用户传递的值。
  • @jweyrich 从技术上讲,它不是“覆盖”任何东西,因为它是一个宏,而不是一个函数。该论点不是“传入”而是被替换的。如果你展开宏,你会看到。但是,正如我在更新中显示的那样,该变量可以被消除,并且也可以以相同的方式存在于原始代码中。
【解决方案3】:

在我看来,这段代码不需要宏。改用数组:

#include <stdio.h>

void write_strongswan_conf(const char *buffer)
{
    FILE *fp = fopen("strongswan.txt", "a");
    if (fp != 0)
    {
        fprintf(fp, "%s\n", buffer);
        printf("Data Written: %s\n", buffer);
        fclose(fp);
    }
}

int main(void)
{
    static const char *conf_string[] =
    {
        "STRONG SWAN CONFIGURATION FILE",
        "THIS IS THE SECOND LINE",
    };

    for (int i = 0; i < sizeof(conf_string) / sizeof(conf_string[0]); i++)
        write_strongswan_conf(conf_string[i]);

    return(0);
}

我很高兴地假设每次写入字符串时打开和关闭文件是明智的。我不相信是这种情况,但原始代码做出了假设。 (打开文件是相当昂贵的;你不会无缘无故地这样做,这对我来说有点无缘无故。)

write_strongswan_conf() 函数中的代码使用fprintf() 而不是fwrite(),因为您只是在编写字符串,这样您就可以避免将换行符连接到缓冲区,从而减少复制的数量。事实上,您甚至不需要知道字符串有多长; fprintf() 会帮你计算。

在生成的代码中确实没有任何地方可以从转换为宏中受益;没有足够大的重复块。初始化的字符串数组可以占用 25 行以及 2 行。如果您愿意,可以将写入函数与其他数据源一起使用。您可以从另一个文件中读取这些行,或者让用户键入它们,或者程序生成它们。它不依赖于 1000 个字符的行(give or take — 998 将是原始代码中字符串的最大安全长度)并且可以处理更长的行。它会错误检查fopen() 调用,如果fopen() 失败,它不会尝试使用空文件指针写入;这样可以避免崩溃。

【讨论】:

  • 是的,我认为该方法要好得多,但我真的很想了解 c 宏的概念。以上所有 cmets 和答案都对我有帮助。非常感谢大家..
猜你喜欢
  • 1970-01-01
  • 2021-12-23
  • 1970-01-01
  • 1970-01-01
  • 2012-08-08
  • 2021-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多