Столкнулся с очень неприятным и неожиданным багом. Суть: при большом уровне вложенности функций становится возможным перезаписать соседнюю (от переменной) область памяти.
Условия:
Компилятор mpif90 (MacPorts gcc48 4.8.3_2)
ОС MacOS X Yosemite 10.10.1 со всеми обновлениями.
Программный код на фортране с очень большим стеком вызовов (глубина вложенности подпрограмм).
Исходные данные:
В глубине кода есть функция function1, в которой объявлены два массива рациональных чисел с локальной (только внутри этой функции) областью видимости. Нигде в остальном коде переменных с такими названиями нет.
real, dimension(x) :: f0, f1 ! x - некая глобальная константа
Оба этих массива явно инициализируются:
f0 = c(x,y,z,:); f1 = f0 !c(x,y,z,:) определён, размерность 4-го измерения массива c равна размерности f0 и f1
Затем вызывается функция function2, которая принимает в качестве аргумента только f0 (который изменяется в процессе выполнения процедуры). При этом перед и после вызова функции стоит распечатка массива f1.
print *, 'f1', f1
temp = function2(f0) !temp - переменная для записи результата функции, f0 изменяется в функции
print *, 'f1', f1
Вывод на консоль (стабильно воспроизводимый результат, сборка кода сохранена):
f1 0.000 0.000 0.000 0.000 0.000 0.000
f1 1.000 0.000 0.000 0.000 0.000 0.000
То есть, происходит изменение значения массива, который не был передан функции в качестве аргумента!
Разборка показала, что в функции function2 массив для аргумента объявлен как:
real, dimension(y) :: farray ! y - некая глобальная константа, y > x, это было очень удобно для организации кодаКоторый дальше явно инициализируется целиком:
farray = 1. ! Массив при такой записи инициализируется полностью
real, dimension(x) :: farray ! x - первая глобальная константа
Вывод: function1 размещает массивы f0 и f1 рядом в памяти. Функция function2 принимает ссылку на адрес в памяти первого массива. Поскольку массив farray объявлен большим, чем f0, то при попытке записи значений в массив f0 "хвост" записывается в соседнюю ячейку памяти, перезаписывая попутно следующий массив (f1) в памяти.
В нормальной операционной системе (а это, имхо, бага именно операционки и системы защиты памяти в ней) должен произойти segmentation fault или page fault - защита памяти от несанкционированной перезаписи. Тут он не происходит, и мы можем спокойно пользоваться такими перезаписанными значениями.
Я пытался воспроизвести баг на "маленьком" коде типа:
module functions
integer :: size = 35
contains
real function modify (n)
implicit none
real, dimension(size), intent(inout) :: n
integer :: i
do i = 1, size
n(i) = n(i) + real(i)
enddo
!print *, n
modify = sum(n)
end function modify
end module functions
program checkbug
use functions
implicit none
real, dimension(3) :: a,b
real :: t
a = (/-1.,-2.,-3./)
b = a
t = modify (a)
print *, a
print *, b
read *
end program checkbug
При значении переменной size до 5 программа не перезаписывает массив и не происходит segfault, при 6 и более - не перезаписывает массив и при завершении программы происходит segfault (end program checkbug). То есть он спокойно всё распечатывает на экран, дожидается ввода с клавиатуры (аналог pause), и только на стадии завершения и сборки мусора падает с segfault.
То есть, видимо, есть какая-то критическая длина стека вызова в MacOS, при превышении которой перестаёт работать система защиты памяти. Что весьма странно, и, вообще говоря, какой-то полный провал с точки зрения безопасности.
Я уже не первый раз сталкиваюсь с этой проблемой из-за сложности нашего кода, но сейчас впервые "пробил" эту беду до её причин.
Это похоже на проблему языка Си (и подобных ему) - по умолчанию отключена проверка на конец массива при записи.
ОтветитьУдалитьЕсли у тебя проблема внутри одной программы, то это не проблема операционки, а вот если программа (не подпрограмма) залезает в память другой программы, то тогда да, проблема операционки.
Не знал про такую тонкость, спасибо!
УдалитьТолько вот тут ( http://stackoverflow.com/questions/1021021/c-element-beyond-the-end-of-an-array ), например, пишется, что доступа при этом не даётся...