使用GNU gfortran的字符串宏

8

如何使用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\"

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

感谢您的帮助。


“-DMYMACRO=\”hello\”” 是正确的方法。它引起了哪些问题? - Vladimir F Героям слава
@VladimirF 在我的示例代码中转义引号可以正常工作,但在实际项目中,构建过程的某些复杂性导致它失败。更改构建过程并不是一个真正的选择,因此如果可能的话,我想找到另一种替代方法。 - Jim Eliot
1
你能具体说明在实际项目中为什么转义引号不起作用吗?没有明确的需求,很难提供解决方案。 - Ross
在构建过程的其他位置,所有编译器标志都用引号括起来,并包含在源文件中。在宏定义周围添加"会导致编译器失败。 - Jim Eliot
@VladimirF 我更新了问题,包括 #define STRINGIFY(x) "x"。我发现它只是创建了一个包含文本“MYMACRO”的字符串。 - Jim Eliot
好的,最终我明白了之前链接问题的区别所在。不幸的是,我不知道解决方案。 - Vladimir F Героям слава
3个回答

9

您尝试使用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的解决方案,那么就不需要继续阅读了。

使用此方式在测试源中进行字符串化的示例如下:

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标准。这是我们在仍然使用字符串化配方依赖于#运算符的情况下所能接受的最接近-traditional的方式。但是如果您以这种方式对Fortran代码进行预处理,则可能会破坏一些Fortran代码;否则gfortran本身就不会强制执行-traditional。对于您的测试程序,您可以安全地省略-std=c89,让cpp符合构建时的默认C标准。但是,如果允许或指示其符合-std=c99或更高版本,则标准将强制识别//作为一行注释的开头(如C ++),从而使包含连接运算符的任何Fortran行在第一个出现处被截断。

当然,如果您正在使用make或其他构建系统来构建要字符串化宏的代码,则会有一种方法告诉构建系统哪些操作构成给定类可编译文件的编译。对于任何要使用字符串化前导部分编译的Fortran源文件fsrc,应指定以下操作:

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

感谢您写了这样全面的回答。关于您提到的第一点,那只是我打错字了——我使用了“.F90”后缀而不是“.f90”。我已经编辑了问题以反映这一点。对于第二个问题,您回答说“您不能单独操纵gfortran来使用字符串化配方”,看起来我要么得找到另一种方法来实现我想要的功能,要么说服项目所有者修改他们的构建过程。您第二个答案的其余部分给了我一个解决方案可以提供。再次感谢。 - Jim Eliot

3

虽然这是一个旧问题并已有答案,但我想在不更改默认预处理器或构建过程的情况下在gfortran中实现宏字符串化。我发现只要该行没有初始引号,预处理器就会执行我想要的操作,因此可以通过使用&符号来断行实现所需的字符串化:

astring = "&
&MYMACRO"

需要注意的是,这种方法只适用于传统的预处理器,并且在使用英特尔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

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


太棒了!虽然使用起来有点繁琐,但对于那些无法更改构建过程的大型项目来说,这是唯一真正的解决方案。 - TooTea

-2

STRINGIFY中去掉x周围的引号对于我解决了这个问题:

#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选项启用所有扩展的预处理器。


1
是的,但重点在于运算符将两个字符串连接起来。您展示的宏实际上什么也没做。 - Vladimir F Героям слава

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接