C 中的循环#defines

circular #defines in C

请注意——我没有编写此代码,我只是想维护/改进它。

我的一个头文件包含这些行:

/* macros to seed the random number generator */
/* This is needed on Windows which throws an error due to 'random' 
not being defined on MingW.  I'll clean it up later. */
#define srandom srand
#define random rand

/* New random seed function. */
#define srand srandom
#define rand random

#define rnd(x)  ((int)(rand() % (x)) + 1)
#define rund(x) ((int)(rand() % (x)))

此外,我有所有四个函数的手册页 randsrandrandomsrandom,所以我假设这些都是有效的 C 函数.

你能帮我理解/弄清楚当我调用 rnd(10) 时会发生什么吗?这是未定义的行为吗?一个定义 "override" 是否以某种方式定义另一个?

(我问是因为我觉得我在实际程序中得到了很多低数字,即使我的测试程序似乎工作正常并且随机分布。)


请注意,我几乎可以肯定,我看到的 "lot of low numbers" 可能是运算符优先级和括号中的错误,导致长布尔值始终计算为 "true"。

看起来第一个预处理器将更新源代码并将 srand 替换为 srandom 然后它将再次更新源代码并将 srandom 替换为 srand。我用 cpp 检查过,看起来没问题。程序也有效。我正在重新考虑 srand 宏,因为 stdlib.h 中有一个函数具有相同的名称,但没有发生任何不好的事情。

您的编译器有一个神奇的选项 -E 来查看扩展。

https://godbolt.org/z/_PIvet

int foo(int x)
{
    return rand(x);
}

int foo(int x)
{
    return rnd(x);
}

扩展到

int foo(int x)
{
    return rand(x);
}

int foo(int x)
{
    return ((int)(rand() % (x)) + 1);
}

顺便说一句,IMO 最好改用内联函数。

递归预处理器宏的作用是well-defined:在扩展宏foo的定义时,如果再次找到名称foo,则保留它。因此,在您的情况下,rand 扩展为 random,后者扩展为 rand,不会进一步扩展。

从技术上讲,如果您的代码还包含 stdlib.h,则这些定义的行为是未定义的,因为如果您定义(或取消定义)名称也是标准库函数名称的宏,则行为是未定义的它由包含的 header 拉入。实际上,如果标准库 headers 只将这些名称定义为函数而不是宏,那么这些宏定义将不会有任何效果。如果他们将名称定义为宏,您将遇到编译错误。

函数 randsrand 由核心 C 语言定义,只要您包含 stdlib.h 就可以使用(某些不提供完整 C 语言的嵌入式实现除外)图书馆)。 randomsrandom是Unix/POSIX的加法,要使用它们需要在#include <stdlib.h>.

前定义一个符号如#define _XOPEN_SOURCE 500

我很困惑这些定义如何解决 MinGW 上的任何问题。如果 MinGW 根本没有定义 random,那么调用 random() 会被预处理器扩展为 random() 并且仍然会尝试调用未定义的函数。这看起来要么是一系列连续的 hack 导致了无害的遗留问题,要么是有人笨手笨脚地解决了一个问题,最终以不同的方式解决了它,但尽管它对解决方案没有贡献,但还是提交了那部分内容。

对于给定的展开,C 预处理器不会多次计算同一个宏展开。也就是一旦识别出来就会打破这个循环。因此,在您的情况下,将发生以下扩展:

rnd(10) // list of available macros: [srandom, random, srand, rand, rnd(x), rund(x)]
((int)(rand() % (10)) + 1) // list of available macros: [srandom, random, srand, rand, rund(x)]
((int)(random() % (10)) + 1) // list of available macros: [srandom, random, srand, rund(x)]
((int)(rand() % (10)) + 1) // list of available macros: [srandom, srand, rund(x)]

在最后一步,预处理器将找不到更多可用的扩展(rand 已经完成)并放弃。

不过,这与您看到的小数字多于大数字的原因无关。这可能与 RAND_MAX 的不同定义有关。在一个非常人为的例子中,如果 RAND_MAX = 10,那么 0 有 18% 的机会出现,而其他数字有 9% 的机会。不仅如此,典型的 rand 实现往往具有较低位的低熵。如果统一分布对您很重要,您可能需要查看标准库之外的内容。

blue paint algorithm 中,一个符号不会计算超过一次。 randrandom返回后被涂成蓝色,所以rand不会再扩展为random