Уж такой элементарный C/С++: может->является

    Вопрос на пять: что напечатает эта простая программа:
        #include <stdio.h>
    
        typedef int a;
        a b = 5;
    
        int main()
        {
           a(b);
           printf("%d\n", b);
           return 0;
        }
    
    Уже натерпевшиеся от своего любимого языка, но ещё не прошерстившие всех бизонов gcc, почувствуют подвох — и правильно. Подсказка номер ноль: это скушает С++, но и простой С не подавится.

    Подсказка один. Вот что она напечатает:
    Ось Компиляторы Результат
    ArchLinux 64 clang 2.9, gcc 4.5.2 0
    Win7 32 Visual С++ 2005, 2008, 2010 1
    но только теперь вопрос другой: а что такое a(b)? Ведь я ключиками повертел: и С++, и С — всё одно. Точнее: clang и gcc печатают одно (0), а вижуальники другое (1).

    Здесь уже должно допереть. Нет? Подсказка два: уберите шум, и разверните тип:
        int(b);
    
    Подсказка два++. Привидение приведения типа запросто может постоять и слева от равна, но теперь уже и Майкрософт выдаст ноль:
        int(b) = 0;
    
    Люди с заточенными под 45o мозгами, вообще ещё и думать не начинали — запастили код в http://ideone.com или закомпиляли прямо из буфера — знаем же на нюхах про xsel? Результат такой механической работы с включенным -Wall и есть подсказка три:
       [aviaconstructor@arch64 tmp]# xsel | gcc -xc++ -Wall -
       <stdin>: In function ‘int main()’:
       <stdin>:9:25: warning: ‘b’ is used uninitialized in this function
    
    Программуля-то простая, какой уж тут C++11! Но если вы начали сразу с книжек Александреску и чураетесь святой простоты С, восполняю пробел. Точнее, ставлю один единственный пробел между типом и скобкой. Для самых недогадливых, подсказка четыре (пять, если по счёту):
        int (b);
    
    В этом месте нашего повествования пню ясно, что a(b) или int(b) — это объявление локальной переменной, то же, что и int b. Она-то и закрывает своей грудью глобальную переменную с таким же именем. Уже в комментах к этому посту мы можем посудачить и подебачить на тему:
    • Почему значение стековой (auto) переменной как правило будет детерминировано даже на системах без обнуления свободной памяти?
    • Может ли какая-нибудь правильная реализация на системах с обнулением памяти при каждом запуске выдавать разные значения?
    • И вообще, что выдают другие компиляторы и среды?
    Кстати, подвигайте переменную по стеку, если интересно. Конечно, там не только нули и единицы. А здесь и сейчас мы поедем дальше.

    Вообще, если озарило до обидного поздно, то вера в человечество может быть восстановлена благим словом, ибо про С и С++ конструкции сказано, что всё, что может быть определением, определением и является. Ещё раз: a(b) смахивает на приведение типа в С++, но на декларацию переменной оно похоже больше. Вот и разгадка не только задачки, но и заглавия нашего поста: стрелочка там — это матзнак следования.

    Всё это было бы нечистой воды курьёзом, когда бы у нас, умных, ползали по коду ну только очень умные баги. Вот классика:
       std::string a("my string");
       std::string b();
    
    Две строки, и одна пуста? Ни-фи-га! Второе — определение беспараметральной функции b, возвращающей строку. Руганётся при использовании, слава Богу! Куда хуже, когда хотели как всегда:
       QMutexLocker lock(&globalLock);
    
    а вышло:
       void MyClass::MyFunc()
       {
          QMutexLocker lock();  // видим?
          ThisFuncShallBeProtectedByGlobalLock();
          ++objectVarCounter;
       }
    
    Даже предупреждения не дождётесь — подумаешь, объявление неиспользуемой функции! И работать будет, вот только без лока (Шишков, прости за сплошные англицизмы). В крайнем примере путали объявление переменной с декларацией функции, а вот совсем другой косяк:
       {
          QMutexLocker(&globalLock); // где переменная?
          ...
    
    Нужно ли объяснять почему такой локер работать будет только на себя? А есть у меня ещё и сказочка: мышка бежала, хвостиком вильнула — бдэмс — и вместо равна натоптала точку с запятой в неположенном месте:
       MyEnum myEnumMask ; MyEnum(i);
    
    Нет, уж этот пример надуман! Кривыми хвостиками и не то выписывали — до сих пор работает! Ну да, ну да. Согласен. Я со всем согласен. Но любимый язык мой — враг мой. Будьте внимательны!
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 79

    • НЛО прилетело и опубликовало эту надпись здесь
        +7
        Угу. Мужики привыкли приведение типов писать как (int)b, т.е. в данному случае: (a)b.
        • НЛО прилетело и опубликовало эту надпись здесь
            +9
            Приведение типов должно бесить по умолчанию. C-style cast вместо заметного static_cast<> — тем более
              +8
              После какого-то ключа они начинают бесить компилятор.
                0
                когда я насчитал четвертый легконабираемый static_cast<> в одном выражении я его возненавидел (даже с code completion) и возжелал владеть слепым десятипальцевым.
                • НЛО прилетело и опубликовало эту надпись здесь
              +2
              Задачка конечно веселая. И очередной раз как бы намекает нам что гибкость С и особенно C++ нужно использовать во благо, а не писать нечитаемый код.
          • НЛО прилетело и опубликовало эту надпись здесь
            –2
            А вы если случайно ложку дёгтя уроните в бочку с мёдом, тоже вот такой вот статьей разразитесь, что дескать мёд какой-то невкусный получился? :)

            Это я к чему? К тому, что программист в любом случае должен быть аккуратным, независимо от того, следит за его косяками язык или нет. И уж не соглашусь, что std::string a() — классика, бред это, по рукам давать программисту прутом железным, чтобы неповадно было.

            «Программа делает то, что вы ей сказали делать, а не то, что хотели бы, чтобы она делала.»
              +5
              Баг — это муха, упавшая в бочку мёда. Я по рукам себя не бью — вытаскиваю муху. Мёд ем.
                +1
                Значит показалось :) Согласитесь, мёд вкусный и полезный? :)
              +9
              Слово «подебачить » стало правильно понятым раза с 5го :)
                0
                побачить-подебачить…
                0
                Прикольный стиль повествования :)
                  +2
                  Поначалу показалось, что перевод, западные статьи часто в подобном стиле написаны.
                    +1
                    Мне вообще +100500 напомнило.
                  0
                  Честно говоря вот читал и ждал когда расскажут почему «1» а не «5»… Может я что-то все-таки не до конца понял?
                    +1
                    очень просто, мы объявляем локальную переменную b, которая в scope main()'a используется вместо глобальной.
                    а так как b не инициализирована то туда попадает мусор
                      +1
                      просто мне кажется что стоило и написать что в результате мусор.

                      А вот то что всегда один и тот же результат — «1» — вот это плохо.

                      Помню когда-то была даже история от кого-то из Microsoft о том, как они убирали кусок 100% неиспользуемого кода и продукт (вроде офис) валился. Разкоменчивали — работал. Как оказалось «мусор» брался именно с того куска, который не использовался, и на результате этого мусора была накручен используемый функционал.

                      Так что детерминированный «мусор» гораздо опасней случайного.

                      В исходном коде лучше бы написали:
                      printf(b==0?"zero":"non-zero"); 
                      

                      или типа того
                        0
                        у меня была похожая ситуация, косяк был не в некорректной инициализации, а в том что я рано память высвобождал, а линки на неё были.
                        в Debug-сборке работало, а в Release валилось на ура, и я никак не мог догнать почему.
                        Логгирование памяти тогда помогло.
                          +3
                          Можете рассказать поподробнее, как вы делали логирование памяти?
                        +2
                        Результат всегда не определен, т.к. локальная переменная не была инициализирована. Более того, в дебаге и в релизе результат может быть разный. В общем, автор статьи просто С++ изучал по ускоренной методике и не знал о существовании старых давно изученных граблей ;)
                        +1
                        Переменную не инициализировали, а на стеке, где она была размещена был мусор.
                        +1
                        >Даже предупреждения не дождётесь — подумаешь, объявление неиспользуемой функции!

                        И это в том месте, где её быть не может! Между прочим, компилятор о большинстве таких грабель предупреждает.
                          0
                          О большинстве грабель предупреждает, но не о всех, которые приведены в тексте.
                            +2
                            Нельзя определять вложенные функции. Объявлять можно.

                            // main.cpp
                            
                            int foo(int bar) { return bar; };
                            
                            int main() {
                            
                              extern int foo();
                            
                              return foo();
                            
                            };
                            
                            
                            // foo.cpp
                            
                            int foo() { return 0; };
                            
                            
                            0
                            Почему значение стековой (auto) переменной как правило будет детерминировано даже на системах без обнуления свободной памяти?

                            Глупый вопрос.
                            Перед выполнением main()'a исполняется код crt, который может оставлять на стеке что угодно.
                            В одной версии crt — на стеке по этому адресу лежит константа, в другой — переменная, которая содержит, например, версию ОСи, и т.д.
                            Может ли какая-нибудь правильная реализация на системах с обнулением памяти при каждом запуске выдавать разные значения?

                            легко, смотрите предыдущий ответ :)
                            даже если не используем crt, то предварительно выполняется системный код, например для WinNT одна из функций это LdrpInitializeProcess().
                              0
                              Хм… На Маке еще интереснее результат (copy/paste с самого верхнего примера):

                              $ ./test
                              32767

                              $ gcc -v
                              Using built-in specs.
                              Target: i686-apple-darwin10
                              Configured with: /var/tmp/gcc/gcc-5666.3~123/src/configure --disable-checking --enable-werror --prefix=/usr --mandir=/share/man --enable-languages=c,objc,c++,obj-c++ --program-transform-name=/^[cg][^.-]*$/s/$/-4.2/ --with-slibdir=/usr/lib --build=i686-apple-darwin10 --program-prefix=i686-apple-darwin10- --host=x86_64-apple-darwin10 --target=i686-apple-darwin10 --with-gxx-include-dir=/include/c++/4.2.1
                              Thread model: posix
                              gcc version 4.2.1 (Apple Inc. build 5666) (dot 3)
                                +5
                                а что здесь интересного?
                                мусор он и есть мусор :)
                                +4
                                Простите за дурацкий вопрос, но почему
                                std::string a("my string");
                                а не
                                std::string a = "my string";
                                Это могло бы убрать изрядную долю двусмысленности кода, по крайней мере объявлением неиспользуемой функции тут явно не пахнет
                                  –4
                                  И заодно зающать конструктор копирования, когда это не надо! explicit нужно ставить в определение конструктора, тогда неоднозначностей будет меньше!
                                    +3
                                    А почему Вы решили, что будет использован конструктор копирования? Компилятор не глупый :)
                                      –2
                                      В данном нет, но в специально написанном классе запросто.
                                        +4
                                        эти строки эквивалентны, если explicit нет
                                        +1
                                        На компилятор надейся, а сам не плошай =)
                                        +2
                                        Шилдт говорит, что в случае
                                        myobj a = 123;
                                        используется обычный конструктор с одним параметром соответствующего типа, так что конструктор копирования лишний раз вызываться не будет.
                                          +2
                                          И верно говорит, т.к. умный компилятор не создает временного объекта для 123 ;)
                                          +1
                                          В обоих случаях будет вызыван один и тот же конструктор std::string(const char*)
                                          Можно предположить, что будет operator= (но его не будет — присваивать можно только к существующему объекту, инициализация — не присваивание и делается конструктором), но без копирующего конструктора в первом случае как обойтись?
                                            0
                                            Ну т.е не копирующего, а string(const char*)
                                            +1
                                            100% нет тут конструктора копирования, это просто другая форма записи.
                                              0
                                              Какая разница какой конструктор будет вызван, когда они делают одно и тоже? В чем провинился copy ctor?
                                              Ну и не надо explicit как мантру повторять, он нужен только там где он нужен и не более того. Есть много мест, где неявное преобразование является желаемым поведением.
                                                0
                                                Про copy ctor вопрос снимается, я Вас неправильно понял.
                                              –1
                                              Да, конечно же конструктор в string не объявлен как explicit, и сам я всегда пишу равно. Но тогда бы и задачки не было. А скобочки по ошибке добавляют часто. Тем более, что:
                                                  MyClass* c1 = new MyClass(); // всё супер!
                                                  MyClass с2(); // а вот тут мы попали
                                              
                                              Благо, в этом случае при использовании с2 будет ошибка
                                                +1
                                                А зачем там вообще скобки во втором случае? Это какая-то нотация или привычка? У меня скобки ассоциируются с функциями, ну или в крайнем случае с приведением типов, но зачем ставить скобки, особенно пустые, около имени переменной?..
                                                  +2
                                                  в конструкторах так не пишете —
                                                  
                                                  class A
                                                  {
                                                  private:
                                                      int a, b;
                                                  public:
                                                      A(int a, int b) : _a(a), _b(b)
                                                      {
                                                      }
                                                  }
                                                  
                                                    –5
                                                    Я пишу так:
                                                    class A
                                                    {
                                                      private:
                                                        int a, b;
                                                    
                                                      public:
                                                        A(int _a, int _b)
                                                        {
                                                          a = _a;
                                                          b = _b;
                                                        }
                                                    }
                                                      –1
                                                      И тратите лишние такты) Списки инициализации не просто так придумали.
                                                        0
                                                        Если бы вместо int был какой-то пользовательский тип, может и потратилось бы, а так — очень сомневаюсь. И к счастью, в моем случае читабельность кода важнее экономии одной-двух процессорных инструкций.
                                                          +2
                                                          Вот не вижу особой разницы, зато потом будет меньше желания накосячить.
                                                        0
                                                        Но так не инициализировать константы.
                                                        0
                                                        Вообще-то, запись
                                                        class A
                                                        {
                                                        private:
                                                            int a, b;
                                                        public:
                                                            A(int a, int b) : a(a), b(b)
                                                            {
                                                            }
                                                        }
                                                        

                                                        прекрасно работает. А использование идентификаторов с подчеркиванием в начале — запрещается Стандартом, так как такие идентификаторы зарезервированы для разработчиков языка/компилятора/стандартных библиотек.
                                                          0
                                                          Ога а подчеркивание в конце не так удобно визуально, поэтому лучше уж m_a писать.
                                                            +5
                                                            вы не совсем правы

                                                            17.4.3.2.1 Global names [lib.global.names]

                                                            Certain sets of names and function signatures are always reserved to the implementation:
                                                            • Each name that contains a double underscore (_ _) or begins with an underscore followed by an uppercase letter (2.11) is reserved to the implementation for any use.
                                                            • Each name that begins with an underscore is reserved to the implementation for use as a name in the global namespace.


                                                            то есть _a вполне корректно внутри класса, но только не в global namespace'e
                                                            а вот _A уже нельзя юзать.
                                                              0
                                                              Да, верно.
                                                            +1
                                                            Да, кстати, а что такое "_a" в вашем примере?
                                                              0
                                                              очепятка :)
                                                                0
                                                                А как должно быть?
                                                                  0
                                                                  я сейчас больше пишу на php, поэтому члены класса мне удобнее обзывать _name, хотя раньше юзал m_name
                                                      +5
                                                      Красивыми граблями устлана дорога в C++
                                                        +1
                                                        я бы предпочел, обойтись без этих «красивостей».
                                                        Иногда пятеро в монитор смотрят и балдеют, что же это такое, и почему, а как… хмммм… бывает весело но потом.
                                                          0
                                                          Да, увы. Вот и статья зи цикла: повеселиться. Вообще, задачка про a(b) показывалась куче народу на протяжении многих лет. Статистика печальная — без нулевой подсказки правильный ответ что такое a(b) дали единицы.
                                                            –1
                                                            По мне так вполне очевидная вещь.
                                                            Может потому что я читал стандарт Си и писал свой компилятор :)
                                                              0
                                                              > Уже натерпевшиеся от своего любимого языка, но ещё не прошерстившие всех бизонов gcc, почувствуют подвох — и правильно

                                                              Без подвохаи те, кто писал синтаксический анализатор С/C++ и знают, что a(b) многим чем может быть. На С всё просто — там сканер заглядывает в таблицу символов. В С++ ситуация сложнее из-за скопов и шаблонов. Из-за последних пришлось придумывать typename, чтобы в теле шаблонизированной функции подсказывать что является типом.
                                                        0
                                                        Странный сумбур: вы с одном проблемы перескакиваете на другую) По теме, каждый уважающий себя С++ программист прочел всего Саттера и Майерса и на подобном его уже не словишь ;)
                                                          +2
                                                          О сколько нам открытий чудных
                                                          Готовят просвещенья дух…

                                                          мб хватит постов в стиле «смотрите как оказывается оно поступает»?
                                                            0
                                                            И стилистика и суть поста — отвратительна. Автор, со своими «подсказочками», «мусипусичками» и прочей ерундой идите преподавать в детский сад.
                                                              0
                                                              И стилистика и изложение решений с многократными подсказочками сильно напоминает эту книгу
                                                              www.ozon.ru/context/detail/id/1908029/

                                                              сейчас под рукой нет, чтобы проверить
                                                                +1
                                                                Хорошо, что вспомнили и эту книжку. Были ещё Майерс, Саттер, а в статье упоминается Александреску. Но только данная статья полностью моя — основная задачка ниоткуда не содрана, но ближе всего будут тексты стандарта, глава 7. И особенно оттуда — Ambiguity resolution.

                                                                  +2
                                                                  Вот примеры оттуда (C++ Standard):
                                                                  struct S {
                                                                     S (int);
                                                                  };
                                                                  
                                                                  void foo ( double a )
                                                                  {
                                                                     S x(int(a));
                                                                     S y((int)a);
                                                                     S z(int());
                                                                  }
                                                                  
                                                                  кто не глядя в стандарт скажет что есть: x, y, z? Это та же тема, что и в статье
                                                                    0
                                                                    много раз видел эту задачу
                                                                  +1
                                                                  Ну и отвратительный же у вас стиль написания. Имейте хоть каплю уважения к читателям.
                                                                    0
                                                                    Уважаемый рецензент, разве я кому-то грубил? кому-то демонстрировал своё неуважение?
                                                                      +1
                                                                      Да нет, никому не грубили. Просто перечитайте еще раз. Такое количество не литературных оборотов, каких-то присказок и просторечий просто невозможно. Из-за этого докопаться до смысла статьи очень трудно.
                                                                        0
                                                                        Извиняйте, если не понравился стиль. Я в тексте и у Шишкова присил прощения, намекая на текст, наверняка вам известный. Ещё, Александра Сергеевича уже цитировал один из рецензентов. Времена, конечно, изменились, и всё-таки разрешите припомнить нашего великого поэта ещё раз в контексте моего повествования:

                                                                        А вижу, я винюсь пред вами,
                                                                        Что уж и так мой бедный слог
                                                                        Пестреть гораздо б меньше мог
                                                                        Иноплеменными словами.
                                                                    0
                                                                    Не получается у меня 0 и -1 под FreeBSD:

                                                                    %gcc -O0 test.c -o test
                                                                    %./test
                                                                    24
                                                                    %gcc -O1 test.c -o test
                                                                    %./test
                                                                    -1
                                                                    %gcc -O2 test.c -o test
                                                                    %./test
                                                                    -1
                                                                    %gcc -O3 test.c -o test
                                                                    %./test
                                                                    -1
                                                                      0
                                                                      Скорее всего, другая версия gcc а точнее glib — от неё в стеке и остаётся мусор, который и показывает неинициализированная переменная. Кстати, у меня результат не зависит от оптимизации. Мусор, конечно, чаще всего вполне определённый — поэтому при перезапуске выдаётся всегда одно и то же значение…

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

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