На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: Qraizer, Hsilgos
  
> Новейшие компиляторы, глобальные переменные и условная компиляция , clang 11, gcc 10. Не собираются старые программы ни в командной строке, ни в Qt
    Народ, я себя отношу к сугубо прикладным программистам.
    (Даже не к программистам вовсе, а к научно-техническим работникам, использующим ПК как мощнейший калькулятор для проведения исследований).

    Так вот, вроде до последнего времени все было отлично как в Linux, так и в Windows.
    Но тут решил поставить на виртуальную машину Debian 11 с суперновейшими компиляторами и начались проблемы.

    1. Еле-еле завел Qt creator, так как в Debian 11 (да и в Ubuntu последних версий) убрали пакет Qt5-default, дающий в том числе профили Qt.
    Полдня соображал, что надо вручную указать путь расположения qmake...

    2. Дальше - больше.
    Стал собирать свои старые программы с глобальными переменными (или типами переменных), подключаемыми в разных модулях.
    Чтобы не было двойного объявления переменных, я использовал подсказанный мне опытными программистами старый прием - условную компиляцию с объявлением макроса.

    Например, в модуле glvar.hpp объявляю глобальные переменные/типы:
    ExpandedWrap disabled
      #ifndef GLVAR
       
      #define GLVAR
       
      // Секция глобальных переменных
      typedef double vXYZ[3];
       
      // Слэш в зависимости от ОС (Linux '/', Windows '\')
      char cSl;
       
      #endif


    А непосредственно в нескольких модулях *.c использую эти переменные/типы:
    ExpandedWrap disabled
      #ifndef GLVAR
      #include "glvar.hpp"
      #endif
       
      // Функция поиска максимума
      void MAX2() {
          // ...
          cSl = '/';
       
          return;
      }


    Раньше, до Debian 11, все работало просто отлично!
    А сейчас сыплет ошибками и пишет, что переменные объявляются несколько раз.

    Подскажите, где копать?

    P.S. Думал, что это в Qt начудили. Но в командной строке тоже самое! И с clang, и с gcc.
    Сообщение отредактировано: Qraizer -
      Цитата mkudritsky @
      Чтобы не было двойного объявления переменных, я использовал подсказанный мне опытными программистами старый прием
      Это были не очень опытные программисты.
      Судя по тексту, есть путаница в терминах. Есть понятие "определение", и есть "объявление". Это не одно и то же. Определение вводит в контекст некую сущность, тогда как объявление лишь информирует о ней. Например, вон, во втором фрагменте кода определена функция MAX2(). Она введена в контекст, компилятор проверил её синтаксис и грамматику и скомпилировал в объектный код. А вот известное понятие "прототип функции" является самым настоящим объявлением. Компилятор информирован о её наличии, параметрах и возвращаемом результате, и этого ему достаточно, чтобы в объектном коде правильно генерировать её вызовы и использоваль её результаты, но самой функции он не видел, ничего там естественно не мог проверить и никакого кода для неё не сгенерировал.
      Определений некой сущности во всей программе должно быть только одно, ибо не может быть так, чтобы у неё было несколько экземпляров, даже если они идентичны. А вот объявлений может быть сколько угодно, потому как компилятору всё равно, сколько раз его уверят в её наличии где-то там.
      Заголовки предназначены как раз для размещения в них объявлений. Их роль заключается в документировании внешнего интерфейса некой подсистемы, а для этого определений не требуется. Скажем <stdio.h> публикует интерфейс подсистемы ввода/вывода, так что в нём будут все необходимые типы, прототипы функций и объявления переменных stdin, stdout и stderr. А тот факт, что определения всех этих сущностей лежат в стандартных библиотеках, знать в общем-то необязательно, т.к. это знание (или его отсутствие) абсолютно никак не влияет на возможность ими свободно пользоваться везде, где подключён <stdio.h>.
      Опытные программисты не говорят, как избежать ошибок дублирования сущностей. Вместо этого они говорят, что определения не должны располагаться в заголовках, там должны быть только объявления. Потому что объявления являются частью интерфейса подсистемы, а определения – частью её реализации. Детали реализации выносить в публичный доступ – это всегда моветон. Правильно оформленный пример выше будет выглядеть примерно так:
      ExpandedWrap disabled
        // glvar.hpp
        #ifndef GLVAR                   // имена для стражей включения лучше делать более уникальными, чем просто имя заголовка
        #define GLVAR                   // я, например, предпочитаю добавлять в их конец GUID проекта, сгенерированный спецом для него
         
        // Секция глобальных переменных
        typedef double vXYZ[3];
         
        // Слэш в зависимости от ОС (Linux '/', Windows '\')
        extern char cSl;                // объявление
        #endif
      ExpandedWrap disabled
        // glvar.cpp
        #include "glvar.hpp"
         
        char cSl;                       // определение
      ExpandedWrap disabled
        // main.cpp
        #include "glvar.hpp"
         
        // Функция поиска максимума
        void MAX2() {
            // ...
            cSl = '/';
         
            return;
        }
      Сообщение отредактировано: Qraizer -
        Однако, с формальной т.з., должно же было происходить так:
        1. Зайдя в первый xxx.c-файл, компилятор (препроцессор) не знал о GLVAR, включил тот блок, откомпилировал и сказал, что норм.
        2. Зайдя во второй yyy.c-файл, компилятор (препроцессор) знал о GLVAR, не включил тот блок, и не должен был компилировать (т.к. неизвестная переменная), выдав ошибку компиляции, а не компоновки.
        Отчего же шаг 2 не произошёл?
          Спасибо за информацию к размышлению!
          Вроде я начинаю понимать, почему так все происходит.

          Почему-то в Debian 11 компиляторы собирают все глобальные переменные из всех модулей в одну большую общую кучу.
          То есть, если в модуле 1.c объявлена переменная int iVar и в модуле 2.c объявлена int iVar, то при сборке выдается сообщение о двойном объявлении переменной iVar.
          А я-то рассчитывал, что в модулях глобальные переменные - только для этих модулей.
          Получается, что в "до Debian 11" все так и было, а в Debian 11 все глобальное объединяется вместе.

          Может надо какую-то опцию компилятору задать? (Ну и ее же в Qt прописать).
            Цитата mkudritsky @
            Например, в модуле glvar.hpp объявляю глобальные переменные/типы:

            Попробуй так:
            ExpandedWrap disabled
              // --------------------------------------------------------------------------
              //  FILE PROJECTVAR.H   2022.03.20          
              // --------------------------------------------------------------------------
              #ifndef __PROJECT_VAR_H__
              #define __PROJECT_VAR_H__
               
              #ifdef THIS_IS_MAIN_MODULE
               
              // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
              int SomeVar;
              // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
               
              #else
               
              // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
              extern int SomeVar;
              // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
               
              #endif
               
              // ..........................................................................
              #endif


            Или так:
            ExpandedWrap disabled
              // --------------------------------------------------------------------------
              //  FILE PROJECTVAR.H   2022.03.20          
              // --------------------------------------------------------------------------
              #ifndef __PROJECT_VAR_H__
              #define __PROJECT_VAR_H__
               
              #ifdef THIS_IS_MAIN_MODULE
               
              // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
              #define PLACE_var
              // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
               
              #else
               
              // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
              #define PLACE_var extern
              // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
               
              #endif
               
              PLACE_var int SomeVar;
               
              // ..........................................................................
              #endif


            В одном единственном модуле:
            ExpandedWrap disabled
              // --------------------------------------------------------------------------
              //  2022.03.20                          
              //  file main.c                        
              //                                    
              // --------------------------------------------------------------------------
              #define THIS_IS_MAIN_MODULE
              // --------------------------------------------------------------------------
              #include "project.h"
              // --------------------------------------------------------------------------
              int main (...)
              {
              ....
            Сообщение отредактировано: ЫукпШ -
              Цитата Славян @
              2. Зайдя во второй yyy.c-файл, компилятор (препроцессор) знал о GLVAR, не включил тот блок, и не должен был компилировать (т.к. неизвестная переменная), выдав ошибку компиляции, а не компоновки.
              Отчего же шаг 2 не произошёл?
              Раздельная компиляция. Компилируя yyy.c компилятор точно так же ничего не знает о xxx.c, а ошибки повторного определения выдает редактор связей (линкер) на следующем за компиляцией этапе.
              Цитата mkudritsky @
              Почему-то в Debian 11 компиляторы собирают все глобальные переменные из всех модулей в одну большую общую кучу.
              Они всегда так делали. Только вот в "голом" C в отличие от C++ для совместимости со старым кодом допускалось двойное определение переменных (не могу быстро найти описание, приведу его чуть позже), возможно, в новых версиях эту возможность отключили по-умолчанию.
              Цитата mkudritsky @
              А я-то рассчитывал, что в модулях глобальные переменные - только для этих модулей.
              Тогда надо было писать statiic int iVar;
              Сообщение отредактировано: Dushevny -
                Цитата Dushevny @
                Только вот в "голом" C в отличие от C++ для совместимости со старым кодом допускалось двойное определение переменных (не могу быстро найти описание, приведу его чуть позже)
                В общем искать надо tentative definition
                Вот две ссылки:
                https://blogs.oracle.com/solaris/post/what-...ntative-symbols - объяснение, как это работет в "голом" C
                https://stackoverflow.com/questions/6220881...e-common-symbol - о том, что в GCC10 по умолчанию включена опция -fno-common, которая такое поведение запрещает.
                Сообщение отредактировано: Dushevny -
                  Цитата Dushevny @
                  Они всегда так делали. Только вот в "голом" C в отличие от C++ для совместимости со старым кодом допускалось двойное определение переменных (не могу быстро найти описание, приведу его чуть позже), возможно, в новых версиях эту возможность отключили по-умолчанию.

                  Dushevny, в С-компиляторах GCC по этому вопросу
                  просто ..опа, специально проверял. (в Fedora)
                  Определение переменных с одним именем в разных
                  модулях, приводит к тому, что компилер считает
                  их одной и той же (физической) переменной.
                  (Без ключевого слова extern !)
                  И никаких предупреждений при этом не делает.
                  Это продолжается уже давно - лет 20, а может и от начала времён.
                  ---
                  Могу лишь посоветовать - никогда не использовать С-компилятор
                  из набора GCC. Использовать С++ - у него такого эффекта нет.
                  Сообщение отредактировано: ЫукпШ -
                    Цитата ЫукпШ @
                    в С-компиляторах GCC по этому вопросу
                    просто ..опа, специально проверял. (в Fedora)
                    Определение переменных с одним именем в разных
                    модулях, приводит к тому, что компилер считает
                    их одной и той же (физической) переменной.
                    (Без ключевого слова extern !)
                    Это не ..опа, а так и должно быть по стандврту. И не только в GCC. Но это можно отключить ключами командной строки.
                      Цитата mkudritsky @
                      А я-то рассчитывал, что в модулях глобальные переменные - только для этих модулей.
                      Получается, что в "до Debian 11" все так и было, а в Debian 11 все глобальное объединяется вместе.

                      Может надо какую-то опцию компилятору задать? (Ну и ее же в Qt прописать).
                      Глобальные переменные являются частью состояния приложения в целом. Конечно они должны быть доступны изо всех модулей, в которых объявлены. Поэтому дублирование их определений является ошибкой, причём скорее архитектурной, чем программной. Но модули могут хранит и свои локальные состояния. Как уже написал Dushevny, для этого определение должно быть предварено static:
                      ExpandedWrap disabled
                        // glvar.hpp
                        #ifndef GLVAR                   // имена для стражей включения лучше делать более уникальными, чем просто имя заголовка
                        #define GLVAR                   // я, например, предпочитаю добавлять в их конец GUID проекта, сгенерированный спецом для него
                         
                        // Секция глобальных переменных
                        typedef double vXYZ[3];
                         
                        // Слэш в зависимости от ОС (Linux '/', Windows '\')
                        static char cSl;                // определение
                        #endif
                      ExpandedWrap disabled
                        // main.cpp
                        #include "glvar.hpp"
                         
                        // Функция поиска максимума
                        void MAX2() {
                            // ...
                            cSl = '/';
                         
                            return;
                        }
                      Статические имена не являются частью публичного интерфейса и поэтому не видны за пределами модуля, в котором определены. Так можно написать, ошибок не будет, просто каждый модуль, подключивший glvar.hpp, получит свою собственную cSl, и изменение одной из них никак не отражается на других.
                      P.S. Несмотря на то, что нарушается принцип размещать в заголовках только объявления, по факту такое размещение не публикует сущности наружу, так что можно считать, что это нарушает другой принцип: предназначения заголовков для публикаций интерфейсов. "Идеальный код" такого содержать не должен, но обычно всем пофик. Опытные программисты не просто знают принципы, они понимают, почему они такие. И поэтому знают, когда можно отойти от буквы закона, не ломая дух закона.

                      Добавлено
                      P.P.S. Да, Dushevny, в C допускаются множественные определения, если они идентичны. Но не всё так просто. Чуть позже подниму Стандарт.
                      Сообщение отредактировано: Qraizer -
                        Вот. Цитата из Стандарт C89:
                        Цитата A.6.5.11 Multiple external definitions
                        There may be more than one external definition for the identifier of an object, with or without the explicit use of the keyword extern, If the definitions disagree, or more than one is initialized, the behavior is undefined ($3.7.2).
                        Т.е. позволяются полностью идентичные определения при условии, что не более одного из них будет содержать инициализатор.

                        Добавлено
                        Ну и немного примеров ещё нашёл в 3.7.2 "External object definitions"
                        ExpandedWrap disabled
                          int i1 = 1;        /* definition, external linkage */
                          static int i2 = 2; /* definition, internal linkage */
                          extern int i3 = 3; /* definition, external linkage */
                          int i4;            /* tentative definition, external linkage */
                          static int i5;     /* tentative definition, internal linkage */
                           
                          int i1; /* valid tentative definition, refers to previous */
                          int i2; /* $3.1.2.2 renders undefined, linkage disagreement */
                          int i3; /* valid tentative definition, refers to previous */
                          int i4; /* valid tentative definition, refers to previous */
                          int i5; /* $3.1.2.2 renders undefined, linkage disagreement */
                           
                          extern int i1; /* refers to previous, whose linkage is external */
                          extern int i2; /* refers to previous, whose linkage is internal */
                          extern int i3; /* refers to previous, whose linkage is external */
                          extern int i4; /* refers to previous, whose linkage is external */
                          extern int i5; /* refers to previous, whose linkage is internal */
                          Цитата Qraizer @
                          // я, например, предпочитаю добавлять в их конец GUID проекта, сгенерированный спецом для него

                          #pragma once ещё не везде что ли работает?
                            По поводу прагм в Стандарте всё плохо. Всё, что о них там написано, помимо определения синтаксиса, это что они предназначены для управления процессом трансляции специфическим и зависящим от реализации способом. Ну и что нераспознанные реализацией прагмы должны молча игнорироваться. Что же касается конкретных прагм, там вообще полный ноль. Т.е. просто нет ни одной Стандартной прагмы. Ситуация ровно как с asm: есть определение синтаксиса ассемблерных вставок и на этом всё, Стандарт оставляет их грамматику полностью на усмотрение реализаций.
                            Так что если нужен кроссплатформенный страж включения, то это не #pragma once.
                              Цитата Dushevny @
                              о том, что в GCC10 по умолчанию включена опция -fno-common, которая такое поведение запрещает


                              Для решения проблемы в стиле "скорой помощи" этот совет оказался самым эффективным!
                              Компиляция прошла отлично следующей строкой:
                              ExpandedWrap disabled
                                clang -m64 -std=c11 -fcommon -O3 main.c src/*.c -lm

                              (тут все мои модули, где без опции -fcommon было двойное объявление переменных, лежат в каталоге src)

                              Для работы среды Qt пришлось опцию -fcommon добавить в проект (файл *.pro):
                              ExpandedWrap disabled
                                QMAKE_CFLAGS += -std=c11 -fcommon


                              Но, конечно, в глобальном плане надо со всем разбираться, чтобы не использовать
                              #define GLOBALVAR
                              Будем разбираться!

                              P.S. Уже 7 лет осваиваю C/C++ как научно-технический работник и никак не могу привыкнуть к его особенностям!
                              Разумеется, в моих программах везде используются функции sin, cos, sqrt, pow... и мне никак не понятно, почему не достаточно объявить
                              #include <math.h>
                              Фантастика! В сборке в командной строке для этого обязателен ключ -lm
                              Причем, этот ключ надо писать в самом конце строки компиляции (посередине он не работает - не может найти математические функции).
                              А теперь еще и этот -fcommon как гром среди ясного неба.
                              P.P.S. Сдается мне, что приближаюсь к типовым программам на C/C++, когда ключи компиляции и сборки занимают по половине экрана текста!
                                Цитата mkudritsky @
                                и мне никак не понятно, почему не достаточно объявить
                                #include <math.h>
                                Компилятору достаточно. Ключ -lm передаётся линкеру. Мне тоже непонятно, почему нельзя было просто сканировать все стандартные библиотеки. MSный линкер, к примеру, такой фигнёй не страдает.
                                Что же до -fcommon... Я всегда предпочитал именно такое поведение. Не надо позволять множественные определения, это чревато глубоко зарытыми проблемами. Однако Qt тот ещё тролль: вот так нагло идти поперёк Стандарта и собирать все глобальные сущности в одну кучу, наплевав на требование раздельной компиляции...
                                1 пользователей читают эту тему (1 гостей и 0 скрытых пользователей)
                                0 пользователей:


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0514 ]   [ 15 queries used ]   [ Generated: 18.05.24, 12:07 GMT ]