多少钱可以创建假的函数,在C宏? [英] How much is it possible to create fake-functions with macros in C?

查看:138
本文介绍了多少钱可以创建假的函数,在C宏?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

人们总是说,宏是不安全的,而且,他们不(直接)型检查他们的争论,等等。更糟糕的:当错误发生时,编译器为intrincate和INCOM prehensible诊断,因为宏只是一个烂摊子。


  

是否有可能的使用的在几乎相同的方式作为一个函数宏,通过
  有安全的类型检查,避免典型的陷阱和的方式,
  编译器给出的右键的诊断。



  1. 我要回答一个肯定的方式这个问题(自动应答)。

  2. 我要告诉你,我已经发现了这个问题的解决。

  3. 标准C99将采用和推崇,有一个统一的背景。

  4. 但(很明显有一个但是),它会确定某种语法,人们将不得不吃的。

  5. 这个特殊的语法打算是最简单的写不亚于最容易理解和/或处理,最大限度地减少形成不良项目的风险,更重要的是,获得从编译器正确诊断消息。

  6. 最后,将研究两种情况:无返回值宏(容易出现的情况)和回归价值宏(不-容易,但更有趣的案例)

让我们快速记得宏产生了一些典型的陷阱。

示例1

 的#define SQUARE(X)X * X
INT I = SQUARE(1 + 5);

I 预期值:36。
真值 I :11(与宏展开: 1 + 5 * 1 + 5 )。 陷阱!

(典型值)的解决方案(例2)

 的#define SQUARE(X)(X)*(X)
INT I =(int)的SQUARE(3.9);

I 预期值:15。
真值 I :11(宏展开后:(INT)(3.9)*(3.9))陷阱!

(典型值)的解决方案(例3)

 的#define SQUARE(X)((X)*(X))

它正常工作与整数和浮点数,但它很容易破碎:

  INT X = 2;
INT I = SQUARE(++ x)的;

I 预期值:9(因为(2 + 1)*(2 + 1) .. )。
的真值i :12(宏展开:((++ x)*(++ x)),这给 3 * 4 )。 陷阱!

在宏类型检查一个很好的方法,可以在这里找到:

不过,我想更多:一些接口或标准语法,并且易于记忆的规则(小)号。
这样做的目的是可以的使用的(不执行)作为宏类似的功能成为可能。
这意味着:写得好假的函数。

这是为什么在某种程度上有意思吗?

我认为这是一个有趣的挑战C.实现

是它有用吗?

编辑:在标准C是无法定义嵌套函数。但是,有时候,人们会preFER才能够定义短(在线)嵌套在其他的内部功能。因此,函数样的原型宏将采取账户的可能性。


解决方案

这答案是在4个部分分为:


    块宏
  1. 建议的解决方案。

  2. 该解决方案的简要介绍。

  3. 宏观原型语法进行了讨论。

  4. 为函数宏建议的解决方案。

  5. 重要更新:的)。我的经纪code

(1)第一种情况。宏块(或无返回值的宏)

让我们首先考虑方便的例子。假设我们需要一个命令
它打印整数,其次是\\ n的广场。
我们决定用宏来实现它。
但是,我们希望的参数由编译器为 INT 进行验证。
我们写的:

 的#define PRINTINT_SQUARE(X){\\
   INT X =(X); \\
   的printf(%d个\\ N,X * X); \\
}


  • 周围的(X)避免几乎所有的陷阱。括号

  • 此外,括号帮助编译器正确诊断语法错误。

  • 宏参数 X 被调用只有宏内部一次。这避免了这个问题的实施例3的缺陷。

  • X的值立即在变量 X 举行。

  • 在宏的其余部分,我们使用变量 X 而不是 X

  • [重要更新:] (这code可破的:见 5 )。

如果我们系统化这门学科,将可避免宏的典型问题。结果
现在,像这样正确打印9:

  INT I = 3;
PRINTINT_SQUARE(我++);

显然,这种方法可以有一个弱点:变量 X 宏可能在程序中其他变量也叫 X冲突中定义。这是一个适用范围的问题。但是,这不是因为宏体已被写入由封闭块问题{} 。这足以处理好每一个作用域的问题,并与内部变量每一个潜在的问题,X 被解决。

这可以说变量 X 是一个额外的对象,也许不希望的。
X 有(​​只)临时持续时间:这是在宏观的创建之初,与开 {,并且它在宏结束时被销毁,以结束}
通过这种方式, X 它工作作为函数参数:创建一个时间变量来保存参数的值,它终于放弃当宏收益 。
我们不承诺,其功能还没有做过任何罪!

更重要的是:当程序员试图称之为宏观与一个错误的参数,编译器给出的相同诊断的功能会给的相同的情况下。

所以,似乎每个宏陷阱已经解决了!

但是,我们有一个小的语法问题,因为你可以在这里看到:

因此​​,当务之急是(我说)来添加做{}而(0)结构的块状宏定义:

 的#define PRINTINT_SQUARE(X)做{\\
   INT X =(X); \\
   的printf(%d个\\ N,X * X); \\
}而(0)

现在,这个做{}而(0)东西的作品很好,但它是反审美。
的问题是,它具有用于程序员没有直观含义。
我建议使用一种有意义的方式,像这样的:

 的#define xxbeg_macroblock做{
#定义xxend_macroblock}而(0)
#定义PRINTINT_SQUARE(X)\\
  xxbeg_macroblock \\
       INT X =(X); \\
       的printf(%d个\\ N,X * X); \\
  xxend_macroblock

(列入} xxend_macroblock 避免了有些含糊不清,而(0) )。
当然,这个语法是不是安全了。
它必须仔细记录,以避免误用。
考虑下面的丑陋例如:

  {printf的xxend_macroblock(你好);

(2)汇总

块定义,如果我们按照纪律作风写他们不返回值可以表现得像函数的宏:

 的#define xxbeg_macroblock做{
#定义xxend_macroblock}而(0)#定义MY_BLOCK_MACRO(分别是Par1,Par2的,......,PARN)\\
  xxbeg_macroblock \\
       desired_type1 temp_var1 =(是Par1); \\
       desired_type2 temp_var2 =(Par2的); \\
       / * ...... * / \\
       desired_typeN temp_varN =(PARN); \\
       / *(做的东西与对象temp_var1,...,temp_varN); * / \\
  xxend_macroblock


  • 系统调用宏 MY_BLOCK_MACRO()语句的,不是的前pression 的:存在什么样,甚至没有无效的没有回报的价值。

  • 宏参数必须只使用一次,在宏观的开始,并通过他们的价值观与块范围实际的临时变量。在宏的其余部分,可以仅使用这些​​变量。

(3)我们能提供的宏参数的接口?

虽然我们解决了参数类型检查的问题,
程序员不能弄清楚什么类型的参数有。
有必要提供某种宏观原型
这是可能的,而且很安全,但我们必须容忍一个有点棘手的语法和一些限制,也。

您可以找出以下行吗?

  xxMacroPrototype(PrintData,INT X;浮动Ÿ;字符* Z; INT N;);
#定义PrintData(X,Y,Z,N){\\
    PrintData数据= {.X =(X),.Y =(Y),.Z =(Z),.N =(N)}; \\
    的printf(%d个%G%S%d个\\ N,data.x,data.y,data.z,data.n); \\
  }
PrintData(1,3.14,你好,4);


  • 第1行定义在原型作为宏观 PrintData

  • 下面的函数宏PrintData声明。

  • 第3行声明一个变量的时间数据它收集宏的所有参数,一下子。

  • 此步骤需要手动程序员小心书面...但它是一个简单的语法,编译器将拒绝(至少)分配给临时变量的错误类型的参数。

  • (但是,编译器会沉默对逆转分配 .X =(N),.N =(X))。

要声明一个原型,我们写 xxMacroPrototype 以2个参数:


  1. 宏的名称。

  2. 的将在宏内部使用类型和局部变量名称的列表。我们称这个项目为 pseudoparameters 宏。


    • pseudoparameters列表已被写入由分号型变量对的列表,分开(及结束)()。


    • 在宏的身体,第一条语句将是这种形式的声明:结果
      宏名富= {.pseudoparam1 =(MacroPar1),.pseudoparam2 =(MacroPar2),...,.pseudoparamN =(MacroParN)}


    • 宏观内部,pseudoparameters被调用为 foo.pesudoparam1 foo.pseudoparam2 ,等等。


xxMacroPrototype()的定义如下:

 的#define xxMacroPrototype(名称args)typedef结构{} ARGS NAME

简单,不是吗?


  • 的pseudoparameters被实现为 typedef结构

  • 这是保证ARGS]是良好构造类型标识符对的列表。

  • 这是保证编译器会给出理解诊断。

  • pseudoparameters列表已经超过了结构声明相同的限制。 (例如,可变大小的数组只可以在列表的末尾)。
    (特别推荐使用指针到而不是可变大小的数组说明符为pseudoparameters。)

  • 这是不能保证NAME是一个真正的宏名称(但这一事实并不太相关的)。结果
    重要的是,我们知道一些结构类型已经被定义为有,关联到宏的参数列表。

  • 这并不保证pseudoparameters列表中,通过提供ARGS以某种方式与真正的宏观参数列表实际上是一致的。

  • 它无法保证一个程序员正确使用此宏里面。

  • 的结构类型声明的范围是一样的,其中 xxMacroPrototype 调用完成的地步。

  • 建议的做法放在一起宏原型紧跟相应的宏定义。

但是,它很容易与那种声明进行惩罚,并且很容易程序员尊重规则。

可以在块宏回归的值?

是的。其实,只要你想就可以获取尽可能多的价值,
通过简单地引用传递参数,如 scanf()的一样。

但你可能在想别的事:

(4)第二种情况。函数宏

对于他们,我们需要一点点不同的方法来声明宏观原型,一个包含返回值的类型。同时,我们必须学习(不硬)技术,让我们保持块宏的安全性,具有我们需要的类型返回值。

可以实现参数的类型检查,如下所示:

在块宏,我们可以声明结构变量名称只是宏观自身内部,结果
因此,保持它隐藏于程序的其余部分。对于函数宏这不能(在标准C99)来完成。我们有宏观的任何调用之前定义类型名称的变量。如果我们愿意付出这个代价,那么我们就可以赚取所需的安全函数宏,与返回的特定类型的值。结果
我们展示了code,用一个例子,然后我们评论吧:

 的#define xxFuncMacroPrototype(RETTYPE,宏观数据,参数)typedef结构{RETTYPE xxmacro__ret__; ARGS}宏观数据xxFuncMacroPrototype(浮动,xxSUM_data,INT X;浮动Ÿ;);
xxSUM_data xxsum;
#定义SUM(X,Y)(xxsum =(xxSUM_data){.X =(X),.Y =(Y)} \\
    xxsum.xxmacro__ret__ = xxsum.x + xxsum.y,\\
    xxsum.xxmacro__ret__)的printf(%G \\ N,SUM(1,2.2));

第一行定义的函数宏原型的语法。结果
一个这样的原型有3个参数:


  1. 类型的回归的价值。

  2. 用来存放pseudoparameters的typedef结构的名称。

  3. pseudoparameters的列表,以分号分隔(并最终)(;)。

返回值是在结构中的附加字段,具有固定名称: xxmacro__ret __ 结果。
此声明,为了安全,因为在结构中的第一个元素。然后pseudoparameters的列表被粘贴。

当我们使用这个接口(如果你让我这样调用它),我们必须遵循一系列的规则,依次是:


  1. 编写原型声明给予3 paramenters到xxFuncMacroPrototype()(例子中的第二行)。

  2. 第2个参数是一个名称 typedef结构,宏观itselfs版本,所以你不用担心,只是使用它(在本例中此类型 xxSUM_data )。

  3. 定义一个变量,其类型是简单的结构类型(在本例: xxSUM_data xxsum; )。

  4. 定义所需的,用参数的适当数量:的#define SUM(X,Y)

  5. 宏的主体必须由括号包围(),以获得EX preSSION(因此,一个返回值)。

  6. 这里面括号,我们可以单独操作一个长长的清单和功能通过使用逗号操作符(,)调用。

  7. 我们需要的第一个操作是通行证的参数X,Y,宏SUM(X,Y),向全局变量 xxsum 。这是通过:

xxsum =(xxSUM_data){.X =(X),.Y =(Y)}

观察到类型的对象是在空气中的与C99语法提供复合文字的援助创建 xxSUM_data 。这个对象的字段由读取参数X,Y,宏,只需一次加满,并用括号包围,以保证安全。结果
然后,我们评估前pressions和功能的列表,所有的人都用逗号分隔的运营商(,)。结果
最后,最后一个逗号后,我们只写了 xxsum.xxmacro__ret __ ,这被认为是逗号前pression最后期限,因而是回归宏值。

为什么所有的东西?为什么 typedef结构
要使用结构优于使用单独的变量,
因为信息被打包在一个对象,并将该数据保持隐藏的程序的其余部分。
我们不希望界定很多变量来保存程序中的每个宏的参数。
相反,通过定义系统 typedef结构关联到一个宏,我们有一个更容易处理这样的宏。

我们能否避免上述外部变量xxsum?
由于复合文字都是左值,我们可以认为,这是可能的。结果
事实上,我们可以定义这样的宏,如图:

但在实践中,我无法找到实现它在一个安全的方式方法。结果
例如,宏SUM(X,Y)以上不能仅此方法实现的。结果,
(我试图使一些技巧与指针到结构+复合文字,但它似乎是不可能的)。

更新:

(5)经纪我的code。

在第1节给出的例子可以打破这种方式(如多德给我看他的评论,下同):

  INT X = 5; / *在宏以外定义X * /
PRINTINT_SQUARE(X);

由于宏内部还有另一个名为对象x(这样: INT X =(X); ,其中 X 是宏 PRINTINT_SQUARE(X))的形式参数,什么是真正通过作为参数是不是价值5宏之外定义的,而是另一个一:垃圾值结果。
为了了解它,让我们展开了上面两行宏展开后:

  INT X = 5;
{INT X =(X);的printf(%d个X * X); }

变量 X 里面的块被初始化...自身不确定的值!结果
在一般情况下,该技术在节块的宏1至3可以以类似的方式被打破开发的,而我们使用来保存参数的结构对象块内声明。

此表明,这种$ C $的c可以被打破,因此是不安全的:


  

不要试图申报里面的宏局部变量来保存
  参数。



  • 有没有解?我回答是:我认为,为了避免在块宏的情况下,这个问题(在第1至3开发的),我们不得不重复我们所做的函数宏,即:申报控股参数结构体在宏以外,就在 xxMacroPrototype()行之后。

这是不那么雄心勃勃,但无论如何,它反应了一个问题:多少是有可能......。在另一方面,我们现在遵循两种情况相同的方法:块和函数宏

People always say that macros are unsafe, and also that they are not (directly) type-checking on their arguments, and so on. Worse: when errors occur, the compiler gives intrincate and incomprehensible diagnostics, because the macro is just a mess.

Is it possible to use macros in almost the same way as a function, by having safe type-checking, avoiding typical pitfalls and in a way that the compiler gives the right diagnostic.

  1. I am going to answer this question (auto-answering) in an affirmative way.
  2. I want to show you the solutions that I've found to this problem.
  3. The standard C99 will be used and respected, to have a uniform background.
  4. But (obviously there is a "but"), it will "define" some kind of "syntax" that people would have to "eat".
  5. This special syntax intends to be the simplest to write as much as the easiest to understand and/or handle, minimizing the risks of ill formed programs, and more importantly, obtaining the right diagnostic messages from the compiler.
  6. Finally, it will study two cases: "non-returning value" macros (easy case) and "returning-value" macros (not-easy, but more interesting case).

Let us quickly remember some typical pitfalls produced by macros.

Example 1

#define SQUARE(X) X*X
int i = SQUARE(1+5);

Intended value of i: 36. True value of i: 11 (with macro expansion: 1+5*1+5). Pitfall!

(Typical) Solution (Example 2)

#define SQUARE(X) (X)*(X)
int i = (int) SQUARE(3.9);

Intended value of i: 15. True value of i: 11 (after macro expansion: (int) (3.9)*(3.9)). Pitfall!

(Typical) Solution (Example 3)

#define SQUARE(X) ((X)*(X))

It works fine with integers and floats, but it is easily broken:

int x = 2;
int i = SQUARE(++x);

Intended value of i: 9 (because (2+1)*(2+1)...). True value of i: 12 (macro expansion: ((++x)*(++x)), which gives 3*4). Pitfall!

A nice method for type-checking in macros can be found here:

However I want more: some kind of interface or "standard" syntax, and a (small) number of easy-to-remember rules. The intent is "be able to use (not to implement)" macros as similar to functions as possible. That means: well written fake-functions.

Why is that interesting in some way?

I think that is an interesting challenge to achieve in C.

Is it useful?

Edit: In standard C is not possible to define nested functions. But, sometimes, one would prefer to be able to define short (inline) functions nested inside other ones. Thus, a function-like prototyped macro would be a possibility to take in account.

解决方案

This answer is divided in 4 sections:

  1. Proposed solution for block macros.
  2. A brief summary of that solution.
  3. Macro-prototype syntax is discussed.
  4. Proposed solution for function-like macros.
  5. (Important update:) Broking my code.

(1.) 1st case. Block macros (or non-returning value macros)

Let us consider easy examples first. Suppose that we need a "command" that prints the square of integer numbers, followed by '\n'. We decided to implement it with a macro. But we want the argument to be verified by the compiler as an int. We write:

#define PRINTINT_SQUARE(X) {    \
   int x = (X);              \
   printf("%d\n", x*x);      \
}

  • The parentheses surrounding (X) avoid almost all pitfalls.
  • Moreover, the parentheses help the compiler to properly diagnose syntax errors.
  • The macro parameter X is invoked only once inside the macro. This avoids the pitfall of Example 3 of the question.
  • The value of X is immediately held in the variable x.
  • In the rest of the macro, we use the variable x instead X.
  • [Important Update:] (This code can be broken: see section 5).

If we systematize this discipline, the typical problems of macros will be avoided.
Now, something like this correctly prints 9:

int i = 3;
PRINTINT_SQUARE(i++);  

Obviously this approach could have a weak point: the variable x defined inside the macro could have conflicts with other variables in the program also called x. This is a scope issue. However, it's not a problem since the macro-body has been written as a block enclosed by { }. This is enough to handle every scope-issue, and every potential problem with the "inner" variables x is tackled.

It could be argued that the variable x is an extra object and maybe not desired. But x has (only) temporary duration: it is created at the beginning of the macro, with the opening {, and it is destroyed at the end of the macro, with the closing }. In this way, x it is working as a function parameter: a temporal variable is created to hold the value of the parameter, and it is finally discarded when the macro "returns". We are not committing any sin that functions have not done yet!

More important: when the programmer attempts to "call" the macro with a wrong parameter, the compiler gives the same diagnostic that a function would give under the same situation.

So, it seems every macro pitfall has been solved!

However, we have a little syntactical issue, as you can see here:

Therefore, it is imperative (I say) to add a do {} while(0) construct to the block-like macro definition:

#define PRINTINT_SQUARE(X) do {    \
   int x = (X);              \
   printf("%d\n", x*x);      \
} while(0)

Now, this do { } while(0) stuff works fine, but it is anti-aesthetical. The problem is that it has no intuitive meaning for the programmer. I suggest the use of a meaningful approach, like this:

#define xxbeg_macroblock do {
#define xxend_macroblock } while(0)
#define PRINTINT_SQUARE(X)        \
  xxbeg_macroblock             \
       int x = (X);            \
       printf("%d\n", x*x);    \
  xxend_macroblock

(The inclusion of } in xxend_macroblock avoids some ambiguity with while(0)). Of course, this syntax is not safe anymore. It has to be carefully documented to avoid misuses. Consider the following ugly example:

{ xxend_macroblock printf("Hello");

(2.) Summarizing

Block-defined macros that do not return values can behave like functions if we write them by following the disciplined style:

#define xxbeg_macroblock do {
#define xxend_macroblock } while(0)

#define MY_BLOCK_MACRO(Par1, Par2, ..., ParN)     \
  xxbeg_macroblock                         \
       desired_type1 temp_var1 = (Par1);   \
       desired_type2 temp_var2 = (Par2);   \
       /*   ...        ...         ...  */ \
       desired_typeN temp_varN = (ParN);   \
       /* (do stuff with objects temp_var1, ..., temp_varN); */ \
  xxend_macroblock

  • A call to the macro MY_BLOCK_MACRO() is a statement, not an expression: there is no "return" value of any kind, not even void.
  • The macro parameters must be used just once, at the beginning of the macro, and pass their values to actual temporary variables with block-scope. In the rest of the macro, only these variables may be used.

(3.) Can we provide an interface for the parameters of the macro?

Although we solved the problem of type-checking of parameters, the programmer cannot figure out what type the parameters "have". It is necessary to provide some kind of macro prototype! This is possible, and very safely, but we have to tolerate a little tricky syntax and some restrictions, also.

Can you figure out what the following lines do?

xxMacroPrototype(PrintData, int x; float y; char *z; int n; );
#define PrintData(X, Y, Z, N) { \
    PrintData data = { .x = (X), .y = (Y), .z = (Z), .n = (N) }; \
    printf("%d %g %s %d\n", data.x, data.y, data.z, data.n); \
  }
PrintData(1, 3.14, "Hello", 4);

  • The 1st line "defines" the prototype for the macro PrintData.
  • Below, the function-like macro PrintData is declared.
  • The 3rd line declares a temporal variable data which collects all the arguments of the macro, at once.
  • This step requires to be manually written with care by the programmer...but it is an easy syntax, and the compiler rejects (at least) the parameters assigned to temporary variables with the wrong type.
  • (However, the compiler will be silent about the "reversed" assignment .x = (N), .n = (X)).

To declare a prototype, we write xxMacroPrototype with 2 arguments:

  1. The name of the macro.
  2. The list of types and names of "local" variables that will be used inside the macro. We will call to this items: pseudoparameters of the macro.

    • The list of pseudoparameters has to be written as a list of type-variable pairs, separated (and ended) by semicolons (;).

    • In the body of the macro, the first statement will be a declaration of this form:
      MacroName foo = { .pseudoparam1 = (MacroPar1), .pseudoparam2 = (MacroPar2), ..., .pseudoparamN = (MacroParN) }

    • Inside the macro, the pseudoparameters are invoked as foo.pesudoparam1, foo.pseudoparam2, and so on.

The definition of xxMacroPrototype() is as follows:

#define xxMacroPrototype(NAME, ARGS) typedef struct { ARGS } NAME

Simple, isn't it?

  • The pseudoparameters are implemented as a typedef struct.
  • It is guaranteed that ARGS is a list of type-identifier pairs that is well constructed.
  • It is guaranteed that the compiler will give understandable diagnostics.
  • The list of pseudoparameters has the same restrictions than a struct declaration. (For example, variable-size arrays only can be at the end of the list). (In particular, it is recommended to use pointer-to instead of variable-size array declarators as pseudoparameters.)
  • It is not guaranteed that NAME is a real macro-name (but this fact is not too relevant).
    What matters is that we know that some struct-type has been defined "there", associated to the parameter-list of a macro.
  • It is not guaranteed that the list of pseudoparameters, provided by ARGS actually coincides in some way with the list of arguments of the real macro.
  • It is not guaranteed that a programmer will use this correctly inside the macro.
  • The scope of the struct-type declaration is the same as the point where the xxMacroPrototype invocation is done.
  • It is recommended practice to put together the macro prototype immediately followed by the corresponding macro definition.

However, it is easy to be disciplined with that kind of declarations, and it is easy to the programmer to respect the rules.

Can a block-macro 'return' a value?

Yes. Actually, it can retrieve as many values as you want, by simply passing arguments by reference, as scanf() does.

But you probably are thinking of something else:

(4.) 2nd case. Function-like macros

For them, we need a little different method to declare macro-prototypes, one that includes a type for the returned value. Also, we'll have to learn a (not-hard) technique that let us to keep the safety of block-macros, with a return value having the type we want.

The typechecking of arguments can be achieved as shown here:

In block-macros we can declare the struct variable NAME just inside the macro itself,
thus keeping it hidden to the rest of the program. For function-like macros this cannot be done (in standard C99). We have to define a variable of type NAME before any invocation of the macro. If we are ready to pay this price, then we can earn the desired "safe function-like macro", with returning values of a specific type.
We show the code, with an example, and then we comment it:

#define xxFuncMacroPrototype(RETTYPE, MACRODATA, ARGS) typedef struct { RETTYPE xxmacro__ret__; ARGS } MACRODATA

xxFuncMacroPrototype(float, xxSUM_data, int x; float y; );
xxSUM_data xxsum;
#define SUM(X, Y) ( xxsum = (xxSUM_data){ .x = (X), .y = (Y) }, \
    xxsum.xxmacro__ret__ = xxsum.x + xxsum.y, \
    xxsum.xxmacro__ret__)

printf("%g\n", SUM(1, 2.2));

The first line defines the "syntax" for function-macro prototypes.
A such prototype has 3 arguments:

  1. The type of the "return" value.
  2. The name of the "typedef struct" used to hold the pseudoparameters.
  3. The list of pseudoparameters, separated (and ended) by semicolon (;).

The "return" value is an additional field in the struct, with a fixed name: xxmacro__ret__.
This is declared, for safety, as the first element in the struct. Then the list of pseudoparameters is "pasted".

When we use this interface (if you let me call it this way), we have to follow a series of rules, in order:

  1. Write a prototype declaration giving 3 paramenters to xxFuncMacroPrototype() (the 2nd line of the example).
  2. The 2nd parameter is the name of a typedef struct that the macro itselfs builds, so you have not worry about, and just use it (in the example this type is xxSUM_data).
  3. Define a variable whose type is simply that struct-type (in the example: xxSUM_data xxsum;).
  4. Define the desired macro, with the appropriate number of arguments: #define SUM(X, Y).
  5. The body of the macro must be surrounded by parenthesis ( ), in order to obtain an EXPRESSION (thus, a "returning" value).
  6. Inside this parenthesis, we can separate a long list of operations and function calls by using comma operators (,).
  7. The first operation we need is to "pass" the arguments X, Y, of the macro SUM(X,Y), to the global variable xxsum. This is done by:

xxsum = (xxSUM_data){ .x = (X), .y = (Y) },

Observe that an object of type xxSUM_data is created in the air with the aid of compound literals provided by C99 syntax. The fields of this object are filled by reading the arguments X, Y, of the macro, just once, and surrounded by parenthesis, for safety.
Then we evaluate a list of expressions and functions, all of them separated by comma operators (,).
Finally, after the last comma, we just write xxsum.xxmacro__ret__, which is considered as the last term in the comma expression, and thus is the "returning" value of the macro.

Why all that stuff? Why a typedef struct? To use a struct is better than use individual variables, because the information is packed all in one object, and the data keep hidden to the rest of the program. We don't want to define "a lot of variables" to hold the arguments of each macro in the program. Instead, by defining systematically typedef struct associated to a macro, we have a more easy to handle such macros.

Can we avoid the "external variable" xxsum above? Since compound literals are lvalues, one can believe that this is possible.
In fact, we can define this kind of macros, as shown in:

But in practice, I cannot find the way to implement it in a safe way.
For example, the macro SUM(X,Y) above cannot be implemented with this method only.
(I tried to make some tricks with pointer-to-struct + compound literals, but it seems impossible).

UPDATE:

(5.) Broking my code.

The example given in Section 1 can be broken this way (as Chris Dodd showed me in his comment, below):

int x = 5;          /* x defined outside the macro */
PRINTINT_SQUARE(x);

Since inside the macro there is another object named x (this: int x = (X);, where X is the formal parameter of the macro PRINTINT_SQUARE(X)), what is actually "passed" as argument is not the "value" 5 defined outside the macro, but another one: a garbage value.
To understand it, let us unroll the two lines above after macro expansion:

int x = 5;
{ int x = (x); printf("%d", x*x); }

The variable x inside the block is initialized... to its own undetermined value!
In general, the technique developed in sections 1 to 3 for block macros can be broken in a similar way, while the struct object we use to hold the parameters is declared inside the block.

This shows that this kind of code can be broken, so it is unsafe:

Don't try to declare "local" variables "inside" the macro to hold the parameters.

  • Is there a "solution"? I answer "yes": I think that, in order to avoid this problem in the case of block macros (as developed in sections 1 to 3), we have to repeat what we did for function-like macros, that is: to declare the holding-parameters struct outside the macro, just after the xxMacroPrototype() line.

This is less ambitious, but anyway it responses the question: "How much is it possible to...?". On the other hand, now we follow the same approach for the two cases: block and function-like macros.

这篇关于多少钱可以创建假的函数,在C宏?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆