Многие из нас, обучаясь программированию ещё в университетах или дома, делали это на языках С/С++. Конечно, всё зависит от времени, в которое начиналось наше знакомство с языками программирования. Скажем, кто-то начинал с Фортрана, другие — с 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)


  1. tmk826
    02.04.2015 14:46
    +5

    Настоящий физик может писать Фортраном на любом языке!


    1. ivorobts Автор
      02.04.2015 16:25
      +3

      Кстати почти без шуток — это так :)


    1. Anton_Menshov
      03.04.2015 07:52
      -1

      Гораздо сложнее физику писать на Фортране цивилизованным образом. И, почти без шуток, это тоже иногда возможно. Если звезды на небе в правильном расположении.


  1. fshp
    02.04.2015 18:57
    -3

    Всё правильно – аргументы в функцию в языке С передаются по значению, таким образом изменить a в функции modify_a не получится. Для этого нужно передать аргумент по ссылке и тогда мы будет работать с той самой a, переданной из вызываемой функции.
    Так вот, «нежданчик» номер «раз» заключается в том, что в C нет ссылок.


    1. MacIn
      02.04.2015 19:27
      +2

      Вы не совсем правы. Это вопрос терминологии, и она сильно разнится от языка к языку.
      В литературе по С++ разделяются понятия «ссылка» и «указатель». До того, если посмотреть массу литературы по другим языкам (втч Фортрану или Паскалю), то можно заметить, что «передать по ссылке» и есть «передать указатель» в терминах этих языков.
      Это в ареале C++ ссылка это исключительно &, Reference.


    1. ivorobts Автор
      02.04.2015 23:05

      Существует 2 способа передать аргументы — by reference и by value. Вы конечно можете как угодно это переводить, но передача аргументов по ссылке в С есть.


  1. bolk
    02.04.2015 19:45
    +3

    «Работа с стрингами»? Стринги девушки носят, а работаем мы со строками.


    1. igrishaev
      02.04.2015 20:26

      глупо


      1. bolk
        02.04.2015 21:39
        +3

        Глупо не уметь пользоваться родным языком.


        1. ivorobts Автор
          02.04.2015 23:10
          -3

          Ну давайте тогда всё писать на великом и могучем! Я думаю, что если вы являетесь разработчиком, то понимаете, что зачастую просто невозможно подобрать аналог иностранному слову для наших технический терминов. Багу в коде вы тоже ошибкой называете? :)
          В случае со стрингами/строками это не так, но я чаще использую иностранный аналог, и дело не в неумении пользоваться родным языком, а в удобстве. Я это понятие что на русском, что на английском говорю одинаково — легче переключаться.


          1. bolk
            02.04.2015 23:13
            +2

            В случае со стрингами/строками это не так
            Если в случае со строками это не так, то весь ваш первый абзац вы написали неизвестно зачем. Багу в коде я называю ошибкой или дефектом.


            1. antyblin
              03.04.2015 03:43

              А компьютер вы называете ЭВМ?


              1. bolk
                03.04.2015 07:06
                +2

                А строки вы называете стрингами?


                1. ivorobts Автор
                  03.04.2015 15:38
                  -1

                  Как вы уже заметили, да. Просто у вас какие то болезненные ассоциации :)


                  1. bolk
                    03.04.2015 16:59
                    +1

                    Не вижу в стрингах ничего болезненного.


          1. Maccimo
            03.04.2015 07:18
            +3

            >> зачастую просто невозможно подобрать аналог иностранному слову для наших технический терминов.

            Но ведь это совсем не тот случай? Для строк есть вполне устоявшееся русскоязычное название.
            Вставать в позу и загаживать речь англицизмами при наличии нормальных русскоязычных терминов IMHO глупо.
            Вы же, в конце концов, не во вконтактике на стене накорябали, а в официальном блоге компании статью разместили.


            1. m0nstr0
              16.04.2015 18:18
              +1

              написали бы тогда: «по моему мнению», а не «IMHO» раз на то пошло.


              1. ivorobts Автор
                16.04.2015 18:23

                Вот такие у нас носители русского языка. Стринги только строками называют, а IMHO — это исконно русское.


              1. MacIn
                21.04.2015 00:08

                ПМСМ тогда уже.


        1. igrishaev
          03.04.2015 06:38
          -3

          эта статья написана технарем для технарей
          технический сленг в ней уместен


    1. aulandsdalen
      02.04.2015 21:48
      -1

      А еще есть фортран-девелоперы, которые юзают стринги в своих продактах.
      Мерзость какая, да.


      1. bolk
        02.04.2015 21:50
        +1

        О чём это вы?


  1. folkl
    02.04.2015 21:13
    -3

    со стрингами есть простая функция trim(), которая уберет все победы слева и справа.


    1. folkl
      03.04.2015 08:33

      Конечно пробелы уберет, а не «победы»