【问题标题】:Is there a way to execute arbitrary code in C preprocessor?有没有办法在 C 预处理器中执行任意代码?
【发布时间】:2019-06-29 16:15:00
【问题描述】:

考虑这个程序:

#define MACRO                                   \
    srand(time(NULL));                          \
    if (rand() % 2)                             \
        printf("A");                            \
    else                                        \
        printf("B");

int main() {
    MACRO;
}

相当于下面的宏扩展程序:

int main() {
    srand(time(NULL));
    if (rand() % 2)
        printf("A");
    else
        printf("B");
}

显然,if 语句是在运行时而不是在宏扩展阶段执行的。如果if 语句是在编译时执行的,并且如果有语法引用(如在 Lisp 中),我们可以(几乎)相等的概率得到程序 A 和 A'。答:

int main() {
    printf("A");
}

和A':

int main() {
    printf("B");
}

有没有办法让 GCC 针对编译时评估的条件发出 A 或 A'?

【问题讨论】:

  • 您是在问预处理器是否可以生成随机数?不,但您可以使用 -D/dev/urandom 的输出传递给编译器,并使用 #if 预处理器指令。
  • gcc -DMACRO=printf("A"); test.cgcc -DMACRO=printf("B"); test.c 怎么样
  • 当预处理器扩展MACRO; 时,它会创建一个空语句srand ... ("B");;,因为您的宏通常以分号结尾。只是说......空语句没有问题。
  • 谢谢@EugeneSh.and @cleblanc。使用-D 选项,我们只需从代码库外部提供宏,对吗?它不会使预处理器做任何不同于我们将宏放在代码库中的工作。
  • 谢谢@ChristianGibbons,这是一个错字。虽然,当我查看macro 标签的描述时,它可能不是我可以放在这里的最好的标签。 :)

标签: c gcc lisp


【解决方案1】:

有没有办法在 C 预处理器中执行任意代码?

不,这是原始 (1989) C 标准的作者有意设计的决定。他们熟悉更强大的宏系统(例如来自 Lisp,它确实允许在编译时进行任意计算),并且他们认为这些让我们很难理解不熟悉的程序的含义。

我不清楚为什么要在编译时进行随机选择。如果您解释您的更大目标,我们可能会更有帮助。

【讨论】:

  • 感谢您的回答。 “为什么?”。我的一位同事问我:“编译提交给服务器的任意 C 代码有多可怕?”。我当时得到的答案是“如果你可以在宏中编写任意代码,那就太可怕了。”然后我尝试在宏中编写一些恶意代码来检查我的答案。我很快意识到这实际上并不是那么直截了当,而且我当时的 Lisp 思维太强了。
  • 其实可以在CPP中执行任意代码。 BoostPP 实现控制结构、数据类型和操作。
  • @okovko 这不符合“任意代码执行”的条件,原因有两个:它不能进行任意 I/O,并且它有固定的迭代次数上限或递归,使某些算法无法实现。
  • @zwol 任何程序都存在同样的限制。 I/O 是基于文件的,虽然不方便,但仍然是 I/O。
  • @okovko 不,这在性质上是不同的。在 CS 理论术语中,我大约 95% 确定预处理器充其量只能计算 primitive recursive 函数,这意味着例如不可能实现 the Ackermann function
【解决方案2】:

您不能在预处理器中执行代码。但是,您可以创建生成代码的代码:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() 
{
    printf("#include <stdio.h>\n");
    printf("\n");
    printf("int main()\n");
    printf("{\n");

    srand(time(NULL));
    if (rand() % 2)
        printf("  printf(\"A\");\n");
    else
        printf("  printf(\"B\");\n");

    printf("}\n");
}

输出:

#include <stdio.h>

int main()
{
  printf("A");
}

或者:

#include <stdio.h>

int main()
{
  printf("B");
}

【讨论】:

  • 是的,这就是我们在 Lisp 宏中引用语法时所做的,对吧?
【解决方案3】:

要根据随机数的二取模结果进行编译,我们需要使用BoostPP

#include <boost/preprocessor/control/if.hpp>
#include <boost/preprocessor/arithmetic/mod.hpp>

BOOST_PP_IF(BOOST_PP_MOD(RAND, 2), printf("A"); printf("B");)

您需要使用-D 选项进行编译,以从/dev/random 分配给RAND,就像在评论中建议的那样。

以下是条件宏的一些实现细节,让您了解它的工作原理。取模运算需要实现数据类型和对它们的运算,这里就不演示了。

#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__

#define COMPL(b) PRIMITIVE_CAT(COMPL_, b)
#define COMPL_0 1
#define COMPL_1 0

#define CHECK_N(x, n, ...) n
#define CHECK(...) CHECK_N(__VA_ARGS__, 0,)
#define PROBE(x) x, 1,

#define NOT(x) CHECK(PRIMITIVE_CAT(NOT_, x))
#define NOT_0 PROBE(~)

#define BOOL(x) COMPL(NOT(x))

#define IIF(c) PRIMITIVE_CAT(IIF_, c)
#define IIF_0(t, ...) __VA_ARGS__
#define IIF_1(t, ...) t

#define IF(c) IIF(BOOL(c))

#define EAT(...)
#define EXPAND(...) __VA_ARGS__
#define WHEN(c) IF(c)(EXPAND, EAT)

以及用法。请注意,WHEN(0) 不会展开,任何其他 WHEN(arg) 都会展开。

WHEN(0) ( \
  printf("A"); \
)
WHEN(1) ( \
  printf("B"); \
)
WHEN(whatever) ( \
  printf("this will expand too"); \
)

查看a concise intro to high level CPPsource for the macros in this example,了解有关上述宏的更多详细信息。

【讨论】:

    猜你喜欢
    • 2011-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-08
    • 2021-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多