什么会导致 GOTO 指令出现分段错误?
What would cause a segmentation fault on a GOTO instruction?
我正在研究一个非常古老的 Fortran 77 代码,称为 LOWTRAN。
它基本上是用于模拟大气光传播的模拟工具。
(如果您想查看完整的 lowtran 代码,可以查看 here,但我认为这对回答问题没有帮助)。
不幸的是,由于该代码最初是为穿孔卡片制作的,所以它适用于现代 input/output 方法,这造成了一些令人讨厌的故障。
这些故障很容易 spot/hard 修复。
为了修复其中一个问题,我别无选择,只能设置一个 IF 语句,该语句包含一个 GOTO 语句,该 GOTO 语句超出了 IF 语句,代码中还有其他内容。
但是,有时,GOTO 本身会导致段错误。它不是随机发生的,而是取决于一些似乎与该 IF 语句无关的变量。
我在两台不同的机器上编译这个项目,其中一台没有段错误。都使用 gfortran
在 windows 机器(没有段错误的机器)上,我使用 gfortran 7.2.0,在 Linux 机器(有段错误的机器)上,我使用 gfortran 4.8.5
(我无法在 linux 机器上更新 gfortran 版本,因为我没有所需的权限)
请注意,当我编译我的修复程序时,两个编译器显然都会发出警告:
Warning: Legacy Extension: Label at (1) is not in the same block as the GOTO statement at (2)
这是解决方法
100
...
...
<Lots of code>
...
...
if(ierror.eq.-1) then
itype = 1
ierror = 0
go to 100
end if
计算机运行的代码不是你的源代码,而是机器代码。编译器从您的源代码生成该机器代码。生成可以或多或少是直接的,因此源代码的一条语句对应于一些连续的机器代码指令。但它不必是直接的。特别是,如果编译器提供 optimizations,源代码行与机器代码指令之间的对应关系可能会中断。在那种情况下,debugger reports as the location of the SEGV这一行可能是错误的。
GOTO语句的简单实现是无条件跳转机器码指令,跳转到有效代码地址。这个简单的实现 永远不会 导致 SEGV. You might be tempted to blame your compiler for being buggy, but that would be a mistake。编译器优化可能使事情变得混乱。您可能在数组访问 near 那个 GOTO 语句中有错误,或者在其目标之后的代码(标记为 100 的语句)。
尝试使用 optimizations turned off 重新编译您的程序(通常使用 -O0
之类的命令行选项)并重新运行您的程序。然后,您应该会在存在无效数组访问的行中看到报告的 SEGV。
感谢 Raedwald,我能够找到实际发生的事情。
编译器优化 "hiding" 分段错误的真正原因。
实际发生的是,有一个巨大的循环使用标签 100 作为其终点的参考。有时,标签 100 上的 GOTO 导致循环再迭代一次,导致数组中的访问冲突。
我通过定义一个新标签解决了这个问题。
我从来没有想过禁用编译器优化,这真的很有帮助。
如果出现与内存相关的错误,这始终是一场斗争 - 没有捷径可走。我可以想象,它与您在下面的示例类似。在大多数情况下,这是一个与 jumping
相关的错误,该错误发生在非常重要的代码的某些部分。
program main
implicit none
c
call hello
end
subroutine hello
implicit none
integer a, i
integer, dimension(:), allocatable :: x
allocate(x(100))
goto 101
100 do i = 1, 100
x(i) = i
end do
return
101 read(*,*) a
c
write(*,*) a
if (a.eq.-1) then
deallocate(x)
go to 100
end if
go to 100
c
end
至于调试器,我建议使用 gdb
(在 Linux 上应该有)。这样更容易找到问题。
当谈到SIGSEGV
时,有时,这种问题是"triggered"一个,讨厌,字节。因此,难以钉牢。另外,请记住,这种错误通常属于 "Heisenbug" 类型:https://en.wikipedia.org/wiki/Heisenbug
更新
以上代码完美地说明了@Raedwald 关于优化的建议。
> gfortran -O0 -o main main.f
> ./main
-1
-1
Program received signal SIGSEGV: Segmentation fault - invalid memory reference.
Backtrace for this error:
#0 0x2B7311C376F7
#1 0x2B7311C37D3E
#2 0x2B73126C926F
#3 0x400C1A in hello_
#4 0x400C95 in MAIN__ at main.f:?
Segmentation fault
> gfortran -O3 -o main main.f
> ./main
-1
-1
>
我正在研究一个非常古老的 Fortran 77 代码,称为 LOWTRAN。
它基本上是用于模拟大气光传播的模拟工具。
(如果您想查看完整的 lowtran 代码,可以查看 here,但我认为这对回答问题没有帮助)。
不幸的是,由于该代码最初是为穿孔卡片制作的,所以它适用于现代 input/output 方法,这造成了一些令人讨厌的故障。
这些故障很容易 spot/hard 修复。
为了修复其中一个问题,我别无选择,只能设置一个 IF 语句,该语句包含一个 GOTO 语句,该 GOTO 语句超出了 IF 语句,代码中还有其他内容。
但是,有时,GOTO 本身会导致段错误。它不是随机发生的,而是取决于一些似乎与该 IF 语句无关的变量。
我在两台不同的机器上编译这个项目,其中一台没有段错误。都使用 gfortran 在 windows 机器(没有段错误的机器)上,我使用 gfortran 7.2.0,在 Linux 机器(有段错误的机器)上,我使用 gfortran 4.8.5
(我无法在 linux 机器上更新 gfortran 版本,因为我没有所需的权限)
请注意,当我编译我的修复程序时,两个编译器显然都会发出警告:
Warning: Legacy Extension: Label at (1) is not in the same block as the GOTO statement at (2)
这是解决方法
100
...
...
<Lots of code>
...
...
if(ierror.eq.-1) then
itype = 1
ierror = 0
go to 100
end if
计算机运行的代码不是你的源代码,而是机器代码。编译器从您的源代码生成该机器代码。生成可以或多或少是直接的,因此源代码的一条语句对应于一些连续的机器代码指令。但它不必是直接的。特别是,如果编译器提供 optimizations,源代码行与机器代码指令之间的对应关系可能会中断。在那种情况下,debugger reports as the location of the SEGV这一行可能是错误的。
GOTO语句的简单实现是无条件跳转机器码指令,跳转到有效代码地址。这个简单的实现 永远不会 导致 SEGV. You might be tempted to blame your compiler for being buggy, but that would be a mistake。编译器优化可能使事情变得混乱。您可能在数组访问 near 那个 GOTO 语句中有错误,或者在其目标之后的代码(标记为 100 的语句)。
尝试使用 optimizations turned off 重新编译您的程序(通常使用 -O0
之类的命令行选项)并重新运行您的程序。然后,您应该会在存在无效数组访问的行中看到报告的 SEGV。
感谢 Raedwald,我能够找到实际发生的事情。
编译器优化 "hiding" 分段错误的真正原因。
实际发生的是,有一个巨大的循环使用标签 100 作为其终点的参考。有时,标签 100 上的 GOTO 导致循环再迭代一次,导致数组中的访问冲突。
我通过定义一个新标签解决了这个问题。
我从来没有想过禁用编译器优化,这真的很有帮助。
如果出现与内存相关的错误,这始终是一场斗争 - 没有捷径可走。我可以想象,它与您在下面的示例类似。在大多数情况下,这是一个与 jumping
相关的错误,该错误发生在非常重要的代码的某些部分。
program main
implicit none
c
call hello
end
subroutine hello
implicit none
integer a, i
integer, dimension(:), allocatable :: x
allocate(x(100))
goto 101
100 do i = 1, 100
x(i) = i
end do
return
101 read(*,*) a
c
write(*,*) a
if (a.eq.-1) then
deallocate(x)
go to 100
end if
go to 100
c
end
至于调试器,我建议使用 gdb
(在 Linux 上应该有)。这样更容易找到问题。
当谈到SIGSEGV
时,有时,这种问题是"triggered"一个,讨厌,字节。因此,难以钉牢。另外,请记住,这种错误通常属于 "Heisenbug" 类型:https://en.wikipedia.org/wiki/Heisenbug
更新
以上代码完美地说明了@Raedwald 关于优化的建议。
> gfortran -O0 -o main main.f
> ./main
-1
-1
Program received signal SIGSEGV: Segmentation fault - invalid memory reference.
Backtrace for this error:
#0 0x2B7311C376F7
#1 0x2B7311C37D3E
#2 0x2B73126C926F
#3 0x400C1A in hello_
#4 0x400C95 in MAIN__ at main.f:?
Segmentation fault
> gfortran -O3 -o main main.f
> ./main
-1
-1
>