company_banner

«Нежданчики» языка Фортран


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

    На этом заканчиваю свой рассказ. Теперь вы точно знаете самые важные различия между С и Фортраном, и если уж придётся написать код на последнем, я думаю, сделаете это не хуже, чем на С, правда? Ну а данный пост будет в помощь.
    Метки:
    Intel 145,59
    Компания
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Похожие публикации
    Комментарии 24
    • +5
      Настоящий физик может писать Фортраном на любом языке!
      • +3
        Кстати почти без шуток — это так :)
        • –1
          Гораздо сложнее физику писать на Фортране цивилизованным образом. И, почти без шуток, это тоже иногда возможно. Если звезды на небе в правильном расположении.
        • –3
          Всё правильно – аргументы в функцию в языке С передаются по значению, таким образом изменить a в функции modify_a не получится. Для этого нужно передать аргумент по ссылке и тогда мы будет работать с той самой a, переданной из вызываемой функции.
          Так вот, «нежданчик» номер «раз» заключается в том, что в C нет ссылок.
          • +2
            Вы не совсем правы. Это вопрос терминологии, и она сильно разнится от языка к языку.
            В литературе по С++ разделяются понятия «ссылка» и «указатель». До того, если посмотреть массу литературы по другим языкам (втч Фортрану или Паскалю), то можно заметить, что «передать по ссылке» и есть «передать указатель» в терминах этих языков.
            Это в ареале C++ ссылка это исключительно &, Reference.
            • 0
              Существует 2 способа передать аргументы — by reference и by value. Вы конечно можете как угодно это переводить, но передача аргументов по ссылке в С есть.
            • +3
              «Работа с стрингами»? Стринги девушки носят, а работаем мы со строками.
              • 0
                глупо
                • +3
                  Глупо не уметь пользоваться родным языком.
                  • –3
                    Ну давайте тогда всё писать на великом и могучем! Я думаю, что если вы являетесь разработчиком, то понимаете, что зачастую просто невозможно подобрать аналог иностранному слову для наших технический терминов. Багу в коде вы тоже ошибкой называете? :)
                    В случае со стрингами/строками это не так, но я чаще использую иностранный аналог, и дело не в неумении пользоваться родным языком, а в удобстве. Я это понятие что на русском, что на английском говорю одинаково — легче переключаться.
                    • +2
                      В случае со стрингами/строками это не так
                      Если в случае со строками это не так, то весь ваш первый абзац вы написали неизвестно зачем. Багу в коде я называю ошибкой или дефектом.
                      • 0
                        А компьютер вы называете ЭВМ?
                        • +2
                          А строки вы называете стрингами?
                          • –1
                            Как вы уже заметили, да. Просто у вас какие то болезненные ассоциации :)
                            • +1
                              Не вижу в стрингах ничего болезненного.
                      • +3
                        >> зачастую просто невозможно подобрать аналог иностранному слову для наших технический терминов.

                        Но ведь это совсем не тот случай? Для строк есть вполне устоявшееся русскоязычное название.
                        Вставать в позу и загаживать речь англицизмами при наличии нормальных русскоязычных терминов IMHO глупо.
                        Вы же, в конце концов, не во вконтактике на стене накорябали, а в официальном блоге компании статью разместили.
                        • +1
                          написали бы тогда: «по моему мнению», а не «IMHO» раз на то пошло.
                          • 0
                            Вот такие у нас носители русского языка. Стринги только строками называют, а IMHO — это исконно русское.
                            • 0
                              ПМСМ тогда уже.
                        • –3
                          эта статья написана технарем для технарей
                          технический сленг в ней уместен
                      • –1
                        А еще есть фортран-девелоперы, которые юзают стринги в своих продактах.
                        Мерзость какая, да.
                        • +1
                          О чём это вы?
                      • –3
                        со стрингами есть простая функция trim(), которая уберет все победы слева и справа.
                        • 0
                          Конечно пробелы уберет, а не «победы»

                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                        Самое читаемое