使用 GNU gfortran 将宏字符串化

Stringify macro with GNU gfortran

如何使用 GNU gfortran 对预处理器宏进行字符串化?我想将宏定义传递给 GNU gfortran,然后将其用作代码中的字符串。

实际上我想这样做:

program test
implicit none
character (len=:), allocatable :: astring
astring = MYMACRO
write (*, *) astring
end program test

然后构建:

gfortran -DMYMACRO=hello test.F90

我尝试创建各种宏,例如:

#define STRINGIFY_(x) #x
#define STRINGIFY(x) STRINGIFY_(x)
...
astring = STRINGIFY(MYMACRO)

但这不适用于 gfortran 预处理器。

我也试过使用不同风格的宏:

#define STRINGIFY(x) "x"
...
astring = STRINGIFY(MYMACRO)

但这只会创建一个包含文本 'MYMACRO'.

的字符串

然后我尝试将宏定义更改为:

-DMYMACRO=\"hello\"

但这在构建过程中引起了不相关的问题。

感谢您的帮助

您尝试使用众所周知的 C 预处理器字符串化方法, 即:

#define STRINGIFY_(x) #x
#define STRINGIFY(x) STRINGIFY_(x)

失败有两个原因,每个原因本身就足够了。

首先也是最简单的,显然是您尝试在其中使用它的源文件 具有扩展名 .f90。此扩展名对 gfortran(以及 GCC 编译器驱动程序,任何其他名称)是:不应预处理的自由格式 Fortran 源代码。 同样 .f95.f03.f08。如果您希望 gfortran 推断来源 文件包含 必须 预处理的自由格式 Fortran 代码,给它一个 扩展名 .F90.F95.F03.F08。请参阅 GCC 文档 这些点数

然而,即使你做了那么简单的事情,第二个原因也很重要。

使用 C 预处理器来预处理 Fortran 源代码与 C 一样古老 (虽然很旧,但比 Fortran 年轻得多)。 gfortran有义务不 打破古老的工作密码;所以,当它调用 C 预处理器时, 它以 传统模式 调用它。 C预处理器的传统模式 是预处理器在第一次标准化之前的行为方式 C 语言 (1989),只要可以确定非标准化行为。在传统的 模式下,预处理器无法识别字符串化运算符“#”,这是 由第一个 C 标准引入。您可以通过调用预处理器来验证这一点 直接点赞:

cpp -traditional test.c

其中 test.c 包含一些使用字符串化方法的尝试。这 尝试失败。

您无法自行哄骗 gfortran 进行字符串化处理。

但有一个解决方法。您可以直接调用cpp,不受传统模式的影响, 预处理要在其中完成字符串化的 Fortran 源代码并传递它的 输出到 gfortran。如果您已经知道这一点并且正在寻找 gfortran-alone 您无需进一步阅读的解决方案。

以这种方式在您的测试源中进行字符串化看起来像:

cpp -std=c89 '-DSTRINGIFY_(x)=#x' '-DSTRINGIFY(x)=STRINGIFY_(x)' '-DMYMACRO=STRINGIFY(hello)' test.f90

其输出为:

# 1 "test.f90"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "test.f90"
program test
implicit none
character (len=:), allocatable :: astring
astring = "hello"
write (*, *) astring
end program test

那个输出就是你想要编译的。您也可以通过以下方式实现:

cpp -std=c89 '-DSTRINGIFY_(x)=#x' '-DSTRINGIFY(x)=STRINGIFY_(x)' \
'-DMYMACRO=STRINGIFY(hello)' test.f90 > /tmp/test.f90 \
&& gfortran -o test /tmp/test.f90

然后你会发现./test存在,执行它会输出hello.

您可以通过进一步细化来消除中间临时文件。您的 F90 来源 代码将编译为 F95,因为后者比前者保守。所以你可以拿 GCC 将编译通过管道传输到其标准输入的源代码这一事实的优势,如果 您可以使用其 -x language 选项告诉它您正在使用哪种语言。这 您可以用这种方式指定的 Fortran 方言是 f77f77-cpp-inputf95f95-cpp-input,其中 -cpp-input 前缀表示 源将被预处理,它的缺席表示它不是。因此

cpp -std=c89 '-DSTRINGIFY_(x)=#x' '-DSTRINGIFY(x)=STRINGIFY_(x)' \
'-DMYMACRO=STRINGIFY(hello)' test.f90 |  gfortran -x f95 -o test -

与之前的解决方案一样有效,减去临时文件,并发出 无伤大雅的警告:

Warning: Reading file '<stdin>' as free form

(注意并保留命令行上的最后一个 - 。这就是告诉 gfortran 编译标准输入。)。 -x f95 的含义带来了额外的 经 cpp 预处理的源未经过预处理的经济性 再次由编译器。

在调用 cpp 时使用选项 -std=c89 需要注意 解释。它的作用是使 cpp 符合最早的 C 标准。 在仍然利用 #-operator,字符串化配方所依赖的,但是你拥抱它 如果以这种方式进行预处理,可能会破坏 一些 Fortran 代码; 否则 gfortran 本身不会强制执行 -traditional。如果是 你的测试程序,你可以安全地省略 -std=c89,允许 cpp 符合 到构建时的默认 C 标准。但如果你允许或指示它 符合 -std=c99 或更高版本,则标准将强制认可 // 作为单行注释的开头(根据 C++),其中任何一行 包含串联运算符的 Fortran 将在 第一次出现。

当然,如果您使用 make 或其他构建系统来构建 您想要字符串化宏的代码,您将有一种方法可以告诉您 构建系统什么动作构成 compiling 给定的 class compilable 文件。对于您想要编译的任何 Fortran 源文件 fsrc 字符串化序言,要指定的操作将是静脉:

cpp -std=c89 '-DSTRINGIFY_(x)=#x' '-DSTRINGIFY(x)=STRINGIFY_(x)' \
'-DMYMACRO=STRINGIFY(hello)' fsrc.f90 | gfortran -x f95 -c -o fsrc.o -

删除 STRINGIFYx 周围的引号解决了我的问题:

#define STRINGIFY(x) x   ! No quotes here
program test
    implicit none
    character (len=:), allocatable :: astring
    astring = STRINGIFY(MYMACRO)
    write(*,*), astring
end program test

编译

gfortran -cpp -DMYMACRO=\"hello\" test.f90

-cpp 选项为所有扩展启用预处理器。

尽管这是一个已回答的老问题,但我想在不更改默认预处理器或构建过程的情况下在 gfortran 中实现宏字符串化。我发现预处理器会做我想做的事,只要行上没有初始引号,因此可以通过用 & 符号换行来实现所需的字符串化:

astring = "&
&MYMACRO"

需要注意的是,这实际上只适用于传统的预处理器,并且例如会破坏 intel ifort 编译器,它太聪明了,不会上当受骗。我当前的解决方案是为 gfortran 定义单独的字符串化宏:

#ifdef __GFORTRAN__
# define STRINGIFY_START(X) "&
# define STRINGIFY_END(X) &X"
#else /* default stringification */
# define STRINGIFY_(X) #X
# define STRINGIFY_START(X) &
# define STRINGIFY_END(X) STRINGIFY_(X)
#endif

program test
implicit none
character (len=:), allocatable :: astring
astring = STRINGIFY_START(MYMACRO)
STRINGIFY_END(MYMACRO)
write (*, *) astring
end program test

它看起来真的很丑,但它确实完成了工作。