C宏指定一个值

C宏指定一个值,c,macros,C,Macros,我有一个表单结构: struct foo { bool has_value; int value; } 我想创建一个宏,可用于按如下方式赋值: ASSIGN(foo) = 0; or ASSIGN(foo) += 5; 当宏为: #define ASSIGN(s) \ s->has_value = true; s->value 但是,宏并不安全。在下面的示例中,if将吃掉ASSIGN宏的第一部分 有没有关于如何在安全的情况下进行赋值的建议?使用逗号运算

我有一个表单结构:

struct foo {
   bool has_value;
   int value;
}
我想创建一个宏,可用于按如下方式赋值:

ASSIGN(foo) = 0;
   or
ASSIGN(foo) += 5;
当宏为:

#define ASSIGN(s) \
    s->has_value = true; s->value
但是,宏并不安全。在下面的示例中,if将吃掉ASSIGN宏的第一部分


有没有关于如何在安全的情况下进行赋值的建议?

使用逗号运算符,并加入parens作为衡量标准

#define ASSIGN(s) \
    ((s)->has_value = true), (s)->value
在if语句中,它将扩展为:

if (cond)
    ((foo)->has_value = true), (foo)->value += 5;
然而,这种用法中的宏是一个非常糟糕的主意。 您几乎总是可以想出滥用代码,这将破坏您的宏

即使它能工作,它也会让未来可能阅读您的代码的程序员感到困惑。 以下是此宏将中断的一种错误方式:

ASSIGN(foo) = M_PI;
printf("Sin of PI/2 = %f\n", sin(ASSIGN(foo) *= 0.5));
您可能希望打印1。
它甚至可能无法编译。

使用逗号运算符,并使用paren作为良好的度量

#define ASSIGN(s) \
    ((s)->has_value = true), (s)->value
在if语句中,它将扩展为:

if (cond)
    ((foo)->has_value = true), (foo)->value += 5;
然而,这种用法中的宏是一个非常糟糕的主意。 您几乎总是可以想出滥用代码,这将破坏您的宏

即使它能工作,它也会让未来可能阅读您的代码的程序员感到困惑。 以下是此宏将中断的一种错误方式:

ASSIGN(foo) = M_PI;
printf("Sin of PI/2 = %f\n", sin(ASSIGN(foo) *= 0.5));
您可能希望打印1。 它甚至可能不会编译

有没有关于如何在安全的情况下进行分配的建议

一般来说,你不能这样做。您不应该对左值使用宏。这是一个可怕的想法,它很可能会导致疯狂和不可能找到错误

这似乎真的是一个错误。在您的情况下,正如我所理解的,您希望创建一个宏来简化表达式,该表达式应该在代码中反复执行

您可以定义具有两个参数的宏,而不是使用简单的单参数宏。这样,它将与代码的其余部分更加兼容,同时使用更加一致的语义(如果安全的话)仍能获得相同的结果

在这里,我称之为ASSIGN_FOO,只是为了将它与您的区别开来:

#define ASSIGN_FOO(x, v) do { (x)->has_value = true; (x)->value = v; } while (0)

struct foo var;
ASSIGN_FOO(var, 123);
如果您想知道0时是否执行{…},请查看

如果您希望宏返回指定的值,尽管这与您期望从正常指定中得到的值相同,那么这不是一个好选项

不使用宏,您可以定义一个内联函数,用声明它。通过这种方式,编译器将函数的代码直接集成到调用程序中,并且在编译程序时函数将完全充当宏,只是它现在功能更强大,因为它可以在更多上下文中使用

inline int __attribute__ ((always_inline)) assign_foo(struct foo *x, int value) {
    x->has_value = true;
    x->value = value;
    return x->value;
}

struct foo var;
assign_foo(var, 123);
除此之外,在更新结构中的值时使用定义的宏没有多大意义,因为它很容易导致不需要的未定义行为,如下所示:

struct foo var;
ASSIGN(var) += 5;
扩展至:

var->has_value = true; var->value += 5; // Undefined behavior, var->value used uninitialized!
这里的解决方案是:

如果您已经知道该值存在,则重新分配has_value=true没有意义,您可以直接执行增量:

var->value += 10;
如果不知道该值是否存在,请使用函数安全地执行该操作:

inline int __attribute__ ((always_inline)) increment_foo(struct foo *x, int value) {
    if (!x->has_value) {
        x->has_value = true;
        x->value = value;
    } else {
        x->value += value;
    }

    return x->value;
}

increment_foo(var, 10);
比较:

struct foo x;
printf("%d\n", ASSIGN(x) = 3);    // Compilation error.
printf("%d\n", ASSIGN_FOO(x, 3);  // Compilation error.
printf("%d\n", assign_foo(x, 3)); // No problem.

struct foo y;
printf("%d\n", ASSIGN(y) += 3);           // Compilation error.
ASSIGN(y) += 3; printf("%d\n", y->value); // Undefined behavior.
printf("%d\n", increment_foo(y, 3));      // No problem.
有没有关于如何在安全的情况下进行分配的建议

一般来说,你不能这样做。您不应该对左值使用宏。这是一个可怕的想法,它很可能会导致疯狂和不可能找到错误

这似乎真的是一个错误。在您的情况下,正如我所理解的,您希望创建一个宏来简化表达式,该表达式应该在代码中反复执行

您可以定义具有两个参数的宏,而不是使用简单的单参数宏。这样,它将与代码的其余部分更加兼容,同时使用更加一致的语义(如果安全的话)仍能获得相同的结果

在这里,我称之为ASSIGN_FOO,只是为了将它与您的区别开来:

#define ASSIGN_FOO(x, v) do { (x)->has_value = true; (x)->value = v; } while (0)

struct foo var;
ASSIGN_FOO(var, 123);
如果您想知道0时是否执行{…},请查看

如果您希望宏返回指定的值,尽管这与您期望从正常指定中得到的值相同,那么这不是一个好选项

不使用宏,您可以定义一个内联函数,用声明它。通过这种方式,编译器将函数的代码直接集成到调用程序中,并且在编译程序时函数将完全充当宏,只是它现在功能更强大,因为它可以在更多上下文中使用

inline int __attribute__ ((always_inline)) assign_foo(struct foo *x, int value) {
    x->has_value = true;
    x->value = value;
    return x->value;
}

struct foo var;
assign_foo(var, 123);
除此之外,在更新结构中的值时使用定义的宏没有多大意义,因为它很容易导致不需要的未定义行为,如下所示:

struct foo var;
ASSIGN(var) += 5;
扩展至:

var->has_value = true; var->value += 5; // Undefined behavior, var->value used uninitialized!
这里的解决方案是:

如果您已经知道该值存在,则重新分配has_value=true没有意义,您可以直接执行增量:

var->value += 10;
如果不知道该值是否存在,请使用函数安全地执行该操作:

inline int __attribute__ ((always_inline)) increment_foo(struct foo *x, int value) {
    if (!x->has_value) {
        x->has_value = true;
        x->value = value;
    } else {
        x->value += value;
    }

    return x->value;
}

increment_foo(var, 10);
比较:

struct foo x;
printf("%d\n", ASSIGN(x) = 3);    // Compilation error.
printf("%d\n", ASSIGN_FOO(x, 3);  // Compilation error.
printf("%d\n", assign_foo(x, 3)); // No problem.

struct foo y;
printf("%d\n", ASSIGN(y) += 3);           // Compilation error.
ASSIGN(y) += 3; printf("%d\n", y->value); // Undefined behavior.
printf("%d\n", increment_foo(y, 3));      // No problem.

这种破坏语法的宏通常是个坏主意,但你可以用

#define ASSIGN(s) ((s).has_value=1,&(s))->value
如果你想让它取左值或者

#define ASSIGN(sp) ((sp)->has_value=1,(sp))->value
如果你想让它接受一个指针

工作示例:

说明:

>简单的s.has_值=1,s.value或s.has_值=1,s.value不起作用,因为逗号运算符完成了从左值到右值的转换,但是s.has_值=1,&s->value或*s.has_值=1,&s、 因为指针解引用*和->总是产生左值,而不管它们的指针参数是左值还是右值。

这种破坏语法的宏通常是个坏主意,但您可以使用

#define ASSIGN(s) ((s).has_value=1,&(s))->value
如果你想让它取左值或者

#define ASSIGN(sp) ((sp)->has_value=1,(sp))->value
如果你想让它接受一个指针

工作示例:

说明:


简单的s.has_值=1,s.value或s.has_值=1,s.value不起作用,因为逗号运算符完成了从左值到右值的转换,但是s.has_值=1,&s->value或*s.has_值=1,&s、 值将被删除,因为指针解引用*和->始终会生成左值,而不管其指针参数是左值还是右值。

我可以在此处声明规范的do not use宏吗?在执行ASSIGNfoo+=5;,值总是事先初始化吗?我建议您定义函数assign_foostruct foo*、int和inc_foostruct foo*,如果您发现自己试图使C看起来像一种不同的语言,您可能应该使用您试图使其看起来像的语言。一个具有重载赋值操作符的C++类可能是你想要的吗?我可以在这里声明规范不使用宏吗?当做赋值+=5时,值总是事先初始化吗?我建议您定义函数assign_foostruct foo*、int和inc_foostruct foo*,如果您发现自己试图使C看起来像一种不同的语言,您可能应该使用您试图使其看起来像的语言。C++类具有重载赋值运算符,这是您想要的吗?