Fortran 将字符串内联传递给子例程调用(其中参数定义了长度),会产生意外结果
我发现这段代码的行为出人意料Fortran 将字符串内联传递给子例程调用(其中参数定义了长度),会产生意外结果,fortran,fortran95,Fortran,Fortran95,我发现这段代码的行为出人意料 module testmodule integer, parameter :: LCHARS = 50 contains subroutine init() call foobar("foobar") end subroutine subroutine foobar(s) character(len=*), intent(in) :: s call bar(s) end subroutine
module testmodule
integer, parameter :: LCHARS = 50
contains
subroutine init()
call foobar("foobar")
end subroutine
subroutine foobar(s)
character(len=*), intent(in) :: s
call bar(s)
end subroutine
subroutine bar(str)
character(len=LCHARS), intent(in) :: str
print *, str
end subroutine
end module
program foo
use testmodule
call init()
end program
此代码打印依赖于编译器的垃圾
我发现问题在于,对于字符串参数,我正在跳过一个带有len=*
的例程,然后将该例程传递给一个具有指定长度的例程
引擎盖下到底发生了什么,在标准中描述了这种行为的地方?我是否应该避免为字符例程参数指定长度,因为这种行为可能在没有警告的情况下随时发生?问题是
bar
需要长度为50的字符串(参见character(len=LCHARS),intent(in)::str
),而传递它的字符串的长度仅为6。用
ifort-全部警告、nodec、接口、声明-gen_接口-全部检查-std test.f90
产生错误
forrtl:severe(408):fort:(18):虚拟字符变量'STR'的长度为50,大于实际变量长度6
据我们所知,所有Fortran参数都是通过引用传递的。在幕后,函数
bar
得到的是一个指向字符串str
开头的指针和一个额外的参数,其值是字符串的长度。因此,bar
将占用50个字符的内存,从str
开头开始,并将其打印到屏幕上。由于您传递的字符串只有6个字符长,剩余的44个字符将是“foobar”之后的下一位内存中的任何字符,这将在运行时有所不同,或者取决于您使用的编译器。参数传递取决于编译器,只要满足标准的要求,但通常是一个字符(len=*)伪参数的接口类似
void foo(字符*s,整数len)
在foo过程的实现中,隐藏的len参数用作字符串长度。OTOH,对于字符(len=somevalue)参数,隐藏的len参数要么被忽略,要么根本不传递,并且过程的代码假定somevalue是字符串的正确长度
正如您所看到的,除非您真的知道自己在做什么,并且可以引用标准中的章节来解释原因,否则您不应该使用LEN=*以外的任何东西。我认为您的代码不符合要求。Fortran 95标准的
12.4.1.1节规定:
12.4.1.1与虚拟数据对象关联的实际参数
[…]
如果标量伪参数的类型为默认字符,则伪参数的长度len
应小于或等于实际参数的长度。伪参数变为
与实际参数最左边的len字符关联
您的速度更快了。这或多或少是正确的,但是Fortran参数不必通过引用传递,尽管对于字符串和数组,这是非常可能的。@VladimirF我假设Fortran标准没有指定参数的传递方式,这只是一个实现细节。但是,我假设对于认为它们是通过引用重新传递不会给您带来太多麻烦。如果您能澄清一下,请随意编辑我的答案。如果您不使用C链接或使用属性值(显然),这应该不会给您带来太多麻烦。我想你的答案很好。更详细地说,禁止给出指定的长度不是更好吗?关于长度的假设根本不能保证,因为代码不一定知道它提供的字符串的长度,编译器也不能总是捕捉到错误。@StefanoBorini:原因是历史。回到恐龙时代,LEN=*并不存在。但是当它被添加时,为了不破坏现有的代码,旧的方法仍然必须保留。