Многие из нас, обучаясь программированию ещё в университетах или дома, делали это на языках С/С++. Конечно, всё зависит от времени, в которое начиналось наше знакомство с языками программирования. Скажем, кто-то начинал с Фортрана, другие — с Basic’a или Delphi, но стоит признать, что доля начавших свой тернистый путь программиста с С/С++ наибольшая. К чему я всё это? Когда перед нами стоит задача изучить новый язык и написать на нём код, мы часто основываемся на том, как бы я это написал на своём «базовом» языке. Сузим вопрос — если нужно написать что-то на Фортране, то мы вспоминаем, как бы это было реализовано на С и делаем по аналогии. Очередной раз столкнувшись с тонкостью языка, которая привела к абсолютно неработающему алгоритму и большой проблеме, эскалированной мне, я решил отыскать как можно больше нюансов языка Фортран (Fortran 90/95), по сравнению с С, с которыми столкнулся лично. Это своего рода «нежданчики», которые ты явно не планировал увидеть, а они бац – и всплыли!
Конечно, речь не пойдёт о синтаксисе — в каждом языке он свой. Я попробую рассказать о глобальных вещах, способных изменить всё «с ног на голову». Поехали!
Передача аргументов в функции
Все мы помним, что таким кодом на С изменить значение переменной a в вызывающей main функции нельзя:
void modify_a(int a)
{
a = 6;
}
int main()
{
int a = 5;
modify_a(a);
return 0;
}
Всё правильно – аргументы в функцию в языке С передаются по значению, таким образом изменить a в функции modify_a не получится. Для этого нужно передать аргумент по ссылке и тогда мы будет работать с той самой a, переданной из вызываемой функции.
Так вот, «нежданчик» номер «раз» заключается в том, что в Фортране всё наоборот! Аргументы передаются в функции по ссылке, и подобный код вполне будет изменять значение a:
a = 5
call modify_a (a)
contains
subroutine modify_a (a)
integer a
a = 6
end subroutine modify_a
end
Думаю, что все понимают проблемы, которые могут появиться от незнания данного факта. Причем проявляться эта специфика может много где, в частности, при работе с указателями, но об этом будет отдельный разговор.
Работа с массивами
По дефолту, индексация массивов в Фортране начинается с 1, а не с 0, как в С. То есть real a(10) дает нам массив от 1 до 10, а в С float a[10] идет от 0 до 9. Тем не менее, мы можем задать массив и как real a(0:100) в Фортране.
Кроме того, многомерные массивы хранятся в памяти в Фортране по столбцам. Таким образом обычная матрица
располагается в памяти так:
Не забываем об этом при работе с массивами, особенно, если передаем их в/из функции на С через библиотеки.
Необъявленные переменные
Фортран по умолчанию не будет ругаться на данные, которые мы не объявили явно, потому как здесь есть понятие неявных типов данных. Пошло это с стародавних времён, и идея заключается в том, что мы сразу можем работать с данными, а тип у них будет определяться в зависимости от первой буквы в имени – во как хитро!
Попытка собрать код с компилятором С предсказуемо выдаст ошибку ‘b: undeclared identifier’:
int main()
{
b = 5;
}
В Фортране сработает на ура:
i = 5
end
Сколько же абсолютно разноплановых ошибок в коде может быть от этого. Поэтому, не забываем добавлять в код IMPLICIT NONE, запрещающее подобные «игры» с неявными объявлениями:
implicit none
i = 5
end
И сразу видим ошибку: error #6404: This name does not have a type, and must have an explicit type. [I]
Кстати, язык Фортран не требователен к регистру, поэтому переменные a и A – это одно и то же. Но это уже синтаксис, о котором я обещался не говорить.
Инициализация локальных переменных
Казалось бы, чем подобная инициализация может быть плоха:
real :: a = 0.0
И чем она отличается от такой:
real a
a = 0.0
Неожиданный сюрприз для разработчиков на С – в Фортране есть принципиальное различие в этом! Если локальная переменная инициализируется в момент декларации, то к ней неявно применяется атрибут SAVE. Что это за атрибут? Если переменная объявлена как SAVE (явно или неявно), то она является статической, а значит инициализируется только при первом заходе в функцию. При последующих входах в функцию сохраняется предыдущее значение. И это может быть совсем не тем, что мы ожидаем. Как совет – избегать подобных инициализаций, и при необходимости использовать атрибут SAVE явно. Кстати, у компилятора даже есть отдельная опция -save, позволяющая менять настройки по умолчанию (выделение на стэке) и делать все переменные статическими (кроме случаев рекурсивных функций и тех переменных, которые явно объявлены как AUTOMATIC).
Указатели
Да, в Фортране тоже есть понятие указателей. Но используются они гораздо реже, потому что выделять память динамически в нем можно и без их помощи, а аргументы итак передаются по ссылке. Стоит отметить, что механизм указателей сам по себе работает в Фортране по-другому, поэтому остановлюсь подробней на этом.
Здесь нельзя сделать указатель на любой объект – только на тот, который объявлен специальным образом. Например, так:
real, target :: a
real, pointer :: pa
pa => a
С помощью оператора => мы ассоциируем указатель pa с объектом a. Не стоит пытаться выполнить операцию присваивания вместо =>. Всё успешно соберётся, но упадёт в рантайме. Так что тем, кто привык просто присваивать указатели в С придётся заставлять писать каждый раз => вместо =. Сначала забываешь, но потом втягиваешься.
Если хотим, чтобы указатель не был ассоциирован с объектом, используем nullify(pa) – это своего рода и инициализация указателя. Когда мы просто объявляем указатель, его статус в Фортране неопределен, и функция, проверяющая его ассоциацию с объектами (associated(pa)) будет работать некорректно.
Кстати, почему нельзя ассоциировать указатель с любой переменной того же типа, как это делается в С? Во-первых, так захотелось в комитете по стандартизации. Шучу. Скорее всего, всё дело в ещё одном уровне защиты от потенциальных ошибок – просто так мы теперь точно не сможем связать указатель со случайной переменной, ну и подобное ограничение дает компилятору больше информации, а, следовательно, больше возможностей для оптимизации кода.
Кроме того, что тип указателя и объекта должны совпадать, а сам объект должен быть объявлен с атрибутом TARGET, есть ещё ограничение и на размерность массивов. Скажем, если мы работаем с одномерными массивами, то и указатель должен быть объявлен соответствующим образом:
real, target :: b(1000)
real, pointer :: pb(:)
Если бы массив был двумерный, то указатель бы был pb(:,:). Естественно, что размер массива в указателе не задается – мы же не знаем, с каким массивом будет ассоциирован указатель. Думаю, логика понятна. После ассоциации, мы можем работать с указателем как обычно:
b(i) = pa*b(i+1)
Что то же самое, что написать b(i) = a*b(i+1). Можно и значение присвоить, например pa = 1.2345.
Таким образом, значение у a будет 1.2345. Интересная особенность указателей Фортрана заключается в том, что с их помощью можно работать с частью массива.
Если мы написали b => pb, то можем работать с 1000 элементами массива b через указатель pb.
Но можно написать и так:
pb => b(201:300)
В этом случае мы будем работать с массивом только из 100 элементов, а pb(1) – это b(201).
Забавно, как можно использовать функцию выделения памяти allocate в случае указателей. Написав allocate(pb(20)) мы выделим дополнительно 20 элементов массива типа real, которые будут доступны только через указатель pb.
Вообщем, человеку привыкшему к С, всё это будет казаться необычным. Но, если начать писать код, то достаточно быстро привыкаешь, и всё начинает казаться удобным.
Разработчик, натолкнувший меня на идею написания этого блога, тоже так думал и активно работал с указателями направо и налево, создавая код, алгоритм которого использует дерево, но не учитывал одну особенность. На Фортран переписывался этот Сишный код:
void rotate_left(rbtree t, node n)
{
node r = n->right;
...
У структуры node есть поля, содержащие указатели node*, например right.
В функции создается локальная переменная r, ей присваивается значение n->right и так далее и тому подобное. Реализация на Фортране получилась такой:
subroutine rotate_left(t, n)
type(rbtree_t) :: t
type(rbtree_node_t), pointer :: n
type(rbtree_node_t), pointer :: r
r => n%right
...
И вот тут, в самом начале, кроется «ошибка ошибок». Мы ассоциировали указатель r с n%right. Изменяя в дальнейшем коде r, мы будем менять и n%right, в отличие от С, где будет изменяться только локальная переменная r. В итоге, всё дерево превратилось непонятно во что. Выход из ситуации — ещё один локальный указатель:
subroutine rotate_left(t, n_arg)
type(rbtree_t) :: t
type(rbtree_node_t), pointer :: n_arg
type(rbtree_node_t), pointer :: r
type(rbtree_node_t), pointer :: n
n => n_arg
r => n%right
...
В этом случае, если мы в дальнейшем меняем ассоциацию у указателя n, то это никак не затронет «внешний» n_arg.
Стринги
Ну и напоследок, одна маленькая особенность, попортившая огромное количество памяти в mixed приложениях (С и Фортран). Как вы думаете, в чем может быть разница при работе с стрингами в С:
char string[80]="test";
И Фортране:
character(len=80) :: string
string = "test"
Ответ легко поможет дать отладчик. В этом случае, в Фортране оставшиеся неиспользованными байты забиваются пробелами. При этом нет типичного для С символа окончания строки /0, поэтому нужно быть предельно аккуратным, передавая стринги из Фортрана в С и обратно. Опять скажу, что для того, чтобы безопасно работать с С и Фортраном, нужно использовать специальный модуль ISO_C_BINDING, который разрешает и данное различие, и много других проблем.
На этом заканчиваю свой рассказ. Теперь вы точно знаете самые важные различия между С и Фортраном, и если уж придётся написать код на последнем, я думаю, сделаете это не хуже, чем на С, правда? Ну а данный пост будет в помощь.
Комментарии (24)
fshp
02.04.2015 18:57-3Всё правильно – аргументы в функцию в языке С передаются по значению, таким образом изменить a в функции modify_a не получится. Для этого нужно передать аргумент по ссылке и тогда мы будет работать с той самой a, переданной из вызываемой функции.
Так вот, «нежданчик» номер «раз» заключается в том, что в C нет ссылок.MacIn
02.04.2015 19:27+2Вы не совсем правы. Это вопрос терминологии, и она сильно разнится от языка к языку.
В литературе по С++ разделяются понятия «ссылка» и «указатель». До того, если посмотреть массу литературы по другим языкам (втч Фортрану или Паскалю), то можно заметить, что «передать по ссылке» и есть «передать указатель» в терминах этих языков.
Это в ареале C++ ссылка это исключительно &, Reference.
ivorobts Автор
02.04.2015 23:05Существует 2 способа передать аргументы — by reference и by value. Вы конечно можете как угодно это переводить, но передача аргументов по ссылке в С есть.
bolk
02.04.2015 19:45+3«Работа с стрингами»? Стринги девушки носят, а работаем мы со строками.
igrishaev
02.04.2015 20:26глупо
bolk
02.04.2015 21:39+3Глупо не уметь пользоваться родным языком.
ivorobts Автор
02.04.2015 23:10-3Ну давайте тогда всё писать на великом и могучем! Я думаю, что если вы являетесь разработчиком, то понимаете, что зачастую просто невозможно подобрать аналог иностранному слову для наших технический терминов. Багу в коде вы тоже ошибкой называете? :)
В случае со стрингами/строками это не так, но я чаще использую иностранный аналог, и дело не в неумении пользоваться родным языком, а в удобстве. Я это понятие что на русском, что на английском говорю одинаково — легче переключаться.bolk
02.04.2015 23:13+2В случае со стрингами/строками это не так
Если в случае со строками это не так, то весь ваш первый абзац вы написали неизвестно зачем. Багу в коде я называю ошибкой или дефектом.
Maccimo
03.04.2015 07:18+3>> зачастую просто невозможно подобрать аналог иностранному слову для наших технический терминов.
Но ведь это совсем не тот случай? Для строк есть вполне устоявшееся русскоязычное название.
Вставать в позу и загаживать речь англицизмами при наличии нормальных русскоязычных терминов IMHO глупо.
Вы же, в конце концов, не во вконтактике на стене накорябали, а в официальном блоге компании статью разместили.
igrishaev
03.04.2015 06:38-3эта статья написана технарем для технарей
технический сленг в ней уместен
aulandsdalen
02.04.2015 21:48-1А еще есть фортран-девелоперы, которые юзают стринги в своих продактах.
Мерзость какая, да.
tmk826
Настоящий физик может писать Фортраном на любом языке!
ivorobts Автор
Кстати почти без шуток — это так :)
Anton_Menshov
Гораздо сложнее физику писать на Фортране цивилизованным образом. И, почти без шуток, это тоже иногда возможно. Если звезды на небе в правильном расположении.