2015-06-30 511 views
1

分段錯誤我花了最近兩天在大型Fortran項目中調試看似無意義的段錯誤。當我將代碼移動到我自己的計算機時,問題就開始了,並且段錯誤出現在一些在多個其他系統上工作良好的代碼中。我最終發現了段錯誤的來源,但它是如此令人驚訝的意外(和編譯器相關),我決定在這裏發佈它。將參數傳遞給子程序

考慮以下MWE:

program dafuq 
    implicit none 
    integer :: a=1 
    integer, parameter :: b=2 

    call foo(a,b) 
    end program dafuq 

    subroutine foo(a,b) 
    implicit none 
    integer, intent(inout) :: a, b 

    a=b !OK 
    b=a !causes segfault 

    end subroutine foo 

我訪問兩個HPC集羣,這與我的筆記本電腦一起讓我檢查這些(偶爾有點老)編譯器:

  • ifort 11.1
  • gfortran 4.1.2
  • gfortran 4.4.7
  • gfortran 4.8.4(最新在回購爲Ubuntu 14.04)

事實證明,所有四個編譯器產生與上面的代碼段錯誤,因爲可變b被聲明爲parameter。因此,在子程序中改變它的值是違規的。我的問題是,只有最新的gfortran在編譯時纔會顯示警告(即使使用-Wall),如果我忽略子例程中的intent規範,這也會消失。我懷疑在C++中使用const變量的相同設置會引發巨大的紅旗。

現在,使其更加模糊,考慮下面的代碼,用數組來代替標量:

program dafuq_array 
    implicit none 
    integer :: a(2)=(/1,1/) 
    integer, parameter :: b(2)=(/2,2/) 

    call foo(a,b) 
    end program dafuq_array 

    subroutine foo(a,b) 
    implicit none 
    integer, intent(inout) :: a(2), b(2) 

    a=b !OK 
    b=a !might cause segfault 

    end subroutine foo 

現在,在這種情況下,最新的gfortran產生段錯誤,而其他三個編譯器不要」牛逼! (其實這就是我之前沒有遇到這個問題的原因:列表中最新的gfortran是我自己的計算機上的)。在所有情況下,我基本上都沒有使用編譯開關,即ifort -o mwe mwe.f,gfortran也是如此。

儘管我找到了段錯誤的原因,並且我對它有所瞭解,但仍有一些錯誤(無意雙關)。

  1. 我是否錯在期待編譯錯誤/警告在這種情況下?或者至少有一個超出「無效內存引用」的運行時錯誤。
  2. 使用數組避免某些編譯器出現此錯誤是否有意義?
  3. 我說得對,在不同系統上遇到的不同行爲是由於編譯器的不同造成的,還是因爲系統特定的細微差別?

回答

4

一般來說,如果Fortran函數參數位於模塊內部,它們只會被類型檢查。例如,如果你把一個子程序模塊中:

module m 
    public 
    contains 
    subroutine foo(a,b) 
     implicit none 
     integer, intent(inout) :: a,b 
     a = b 
     b = a 
    end subroutine 
end module 

program p 
    use m 
    implicit none 
    integer :: a 
    integer, parameter :: b = 2 
    a = 1 
    call foo(a,b) 
end program 

編譯給出了錯誤:

gfortran 4.6.4

test.f90:35.15: 

    call foo(a,b) 
       1 
Error: Non-variable expression in variable definition context (actual argument to INTENT = OUT/INOUT) at (1) 

ifort 13.0.1

test.f90(35): error #6638: An actual argument is an expression or constant; this is not valid since the associated dummy argument has the explicit INTENT(OUT) or INTENT(INOUT) attribute. [2] 
    call foo(a,b) 
---------------^ 
compilation aborted for test.f90 (code 1) 

如果你不能夠將代碼添加到代碼中,您也可以考慮啓用自動交互面部和警告(ifort中的-gen-interfaces -warn all)以啓用對不在模塊中的函數進行參數檢查。

+0

感謝您的快速回答。我在開發過程中不時使用'-gen-interfaces -warn interfaces',但是該項目是f77和f90-樣式部分的混合體,大多數舊例程缺少意圖規範。不幸的是,自動接口無法找到這些錯誤沒有意圖。 –

2

對於希望發生錯誤的人來說,你沒有錯(儘管你不能指望這種情況),GNU Fortran 5.1.0確實爲你的第二個測試用例提供了警告。

dafuq.f90:6:13: 

    call foo(a,b) 
      1 
Warning: Named constant ‘b’ in variable definition context (actual argument to INTENT = OUT/INOUT) at (1) 

同時還要注意一些編譯器做工精細,有的不進行編譯,一些內存設計缺陷,使用數組一些工作,他們沒有工作與標量都是合理的結果。一旦違反標準,您的代碼就不再是Fortran,未定義的行爲意味着任何結果都是正確的。

+2

您可能希望出現錯誤,但您無法期待出現錯誤 - 一般情況下包括編譯主程序後編寫外部程序的情況。 – IanH

+0

「違反標準」究竟意味着什麼? 「參數」變量隨後分配給的事實?我同意,這可能會(並且確實)導致未定義的行爲。我的觀點是,編譯器可能想告訴你,你將要做一些非標準的事情。但從@IanH提出的觀點來看,我懷疑編譯器在技術上可能不可能一致地檢查這一點。 –

+1

@AndrasDeak標準不允許在變量上下文中使用參數,或者在意圖輸出或輸入時使用參數。編譯器可以警告某些情況,但要抓住它們很難做到,在某些情況下可能根本不可能。 – casey