1. C++ / Говнокод #17064

    +53

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    17. 17
    18. 18
    19. 19
    20. 20
    21. 21
    22. 22
    23. 23
    24. 24
    25. 25
    26. 26
    const char *GetExternalFilesDir ()
      {
        assert (started);
        static std::string s="";
        if (s.length()==0)
        {
          LOGI("Try get external files dir");
          jclass c; jmethodID m; jobject o;
    
          c = env->FindClass        ("android/app/NativeActivity");
          m = env->GetMethodID      (c, "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
          o = env->CallObjectMethod  (appState->activity->clazz, m, NULL);
    
          c = env->GetObjectClass    (o);
          m = env->GetMethodID      (c, "getAbsolutePath", "()Ljava/lang/String;");
          o = env->CallObjectMethod  (o, m);
          jstring jo = (jstring)o;
    
          const char *path = env->GetStringUTFChars(jo, NULL);
          s=path;
          env->ReleaseStringUTFChars(jo, path);
          s+='/';
          LOGI("Path for program's data files is %s", s.c_str());
        }
        return s.c_str();
      }

    Этот код был написан одним очень известным в очень узких кругах человеком. Будем надеяться, что он придет в обсуждение и прокомментирует его.

    Запостил: DlangGovno, 04 Ноября 2014

    Комментарии (83) RSS

    • В чём ГК? В if (s.length()==0) при том, что строка инициализируется тут же?
      Ответить
      • > В чём ГК?
        В const char *.

        Ну и не потокобезопасно, но на это, скорее всего, пофиг.
        Ответить
        • А что в const char? Не было бы static, был бы возврат указателя на поле локального объекта. А так вроде норм.
          Ответить
          • > А что в const char?
            Да ничего особо страшного. Просто потом автор к нему по-любому будет конкатенировать имена файлов. А с джвумя const char * (второй - литерал с именем файла) это делать малость противно.
            Ответить
      • Она инициализируется "тут же" только один раз (static). Хотя, имхо, стоит сделать функцию-член и static из функции станет обычным (не статическим) членом класса.
        Ответить
        • Ах да, теперь вижу. static, да ещё и return c_str. Убивать за такое.
          Ответить
          • Убивать надо если без статика.

            А со статиком UB'а нет*. Да и переменная только для чтения, поэтому бояться за буфер, возвращаемый c_str не придётся.

            * если джва потока не войдут в GetExternalFilesDir() в первый раз одновременно
            Ответить
            • Если не ошибаюсь, в c++11 дается гарантия инициализации static только в одном потоке.
              переменную только для чтения можно познакомить с const_cast. Надо копию возвращать. Полюбэ.
              Ответить
              • > гарантия инициализации static
                А чем она поможет этому коду?

                s атомарно и ровно один раз проинициализируется пустой строкой, а дальше оба потока войдут в if...
                Ответить
                • ну да, надо доступ на запись к s синхронизировать. Строки 20,22.
                  Ответить
                  • > синхронизировать
                    Синхронизировать с чем? Видимо с чтением этой переменной в строках 5 и 25. Т.е. всю функцию защитить мутексом.
                    Ответить
                  • Не поможет - из функции возвращается указатель на кишки строки, пока ты их читаешь, из другого потока кто-то их может менять. Это UB.
                    Поэтому мутекс надо брать перед вызовом функции и не отпускать до окончания чтения кишков.

                    Проще сделать s thread-local.
                    Ответить
                    • > Проще сделать s thread-local.
                      Ну либо понадеяться на единственность вызова инициализатора, и тупо вынести всё получение строки в отдельную функцию. Как-то так, например:
                      const char * GetExternalFilesDir() {
                          static std::string s(GetExternalFilesDirImpl());
                          return s.c_str();
                      }
                      Ответить
                      • Кстати, c++98, емнип, не гарантирует единственность вызова. А кто-нибудь помнит, как себя ведут разные компиляторы в таком случае?
                        Ответить
                        • Единственность вызова при работе в однопоточном режиме? Думаю, гарантирует.

                          Про многопоточную среду c++98 не слышал и никаких гарантий тут дать не может.
                          Ответить
                          • > Про многопоточную среду c++98 не слышал и никаких гарантий тут дать не может.
                            Само собой.

                            Но ведь почти все реализации стандарта знают о потоках. Так что можно надеяться хотя бы на implementation defined на конкретных компиляторах. Вот поэтому и спрашиваю ;)
                            Ответить
                          • > единственность вызова при работе в однопоточном режиме
                            P.S. Забавы ради попробовал вызвать функцию из инициализатора статика, лежащего в ней же... terminate called after throwing an instance of '__gnu_cxx::recursive_init_error'.
                            Ответить
                    • > мутекс надо брать перед вызовом функции и не отпускать до окончания чтения кишков
                      А учитывая то, что возвращается const char *, с которым могут сделать всё, что угодно - мутекс придется взять раз и навсегда. Ну либо отпускать вручную с вызывающей стороны, что тоже не айс...
                      Ответить
                      • В смысле, внутренний указатель никто тронуть не может, саму строку тоже поменять никто не может.
                        Ответить
                        • Если джва потока одновременно ворвались в блок 6-24 - там уже всё что угодно может быть. И внутренний указатель распидорасят, и строку... Да еще и вернут что-нибудь кривое.

                          Не забывай, на сотиках стоят ARM'ы с достаточно мягкой моделью памяти. С двухъядерным ARM'ом "авось проканает" не проканывает, в отличие от интелей.
                          Ответить
                        • А, всё, я затупил. Мутекса вокруг всей функции хватит, ведь благодаря ему джва потока не прорвутся в инициализацию. И даже отношение happens before будет соблюдаться (записали строку и отпустили мутекс happens before захватили мутекс и вернули строку)...
                          Ответить
    • 10-12, 14-16 - недотарасоформатирование.
      Ответить
      • Сишко-приведение в 17.
        Ответить
        • Ебля в жопу с JNI - 08-21
          Ответить
          • Вы просто не умеет его гововить. Никаких статический состояний точно не нужно.

            Если эта функция должна дёргаться из плюсового кода - нужно возвращать std::string.
            Если из жабы - тип возврата должен быть jstring, нужно аллоцировать строку через env, пусть жаба её сама освобождает.
            Ответить
            • а если из сишного?
              Ответить
              • Возрващать char * и требовать, чтобы пользователь не забыл освободить строку после использования.
                Ответить
                • > чтобы пользователь не забыл освободить строку после использования
                  +1

                  Иначе получается жопа, из-за которой функцию уже никогда не переделать в возвращающую разные значения и, при этом, реентерабельную. Хотя, в случае данного кода, на это можно забить болт - каталог во время работы не меняется.

                  P.S. Ну либо по канонам winapi возложить ответственность за выделение памяти на пользователя функции. Но ему это не понравится.
                  Ответить
                • Или возвращать char* на константный буфер, с которым ничего не случится.
                  А, в сишке же нету const char*, да? Тогда не знаю.
                  Ответить
                  • > в сишке же нету const char*
                    Есть.
                    Ответить
                    • Тогда возврат на внутренний буфер это ок.
                      Если он не меняется в течение работы программы. То есть функция ведёт себя так, будто возвращает указатель на какую-то константу.
                      Ответить
                      • > возврат на внутренний буфер это ок
                        Ну не особо ок, если функция может возвращать разные значения. Из-за подобной питушни со strtok жопы у разрабов стандартной либы пригорели, когда появились потоки.

                        > Если он не меняется в течение работы программы.
                        Для таких случаев - ок. Если не забыть написать в доке, что не надо вызывать free() на результате этой функции.

                        > ведёт себя так, будто возвращает указатель на какую-то константу
                        Но ведь она на самом деле возвращает указатель на какую-то константу...
                        Ответить
                  • > А, в сишке же нету const char*, да?

                    Есть. Я лично периодически использую const для индикации владения. Если функция возвращает неконстантный указатель, она как-бы-говорит-нам, что содержимое полностью принадлежит нам, мы можем туда гадить и отвечаем за его удаление.

                    > с которым ничего не случится
                    Пока не появится второй поток? Кмк, проще быстренько аллоцировать строку, чем постоянно платить за синхронизацию.
                    Ответить
                    • > быстренько аллоцировать строку
                      И слазить в жабу по JNI, не забываем.

                      Хотя для файлов - насрать. Копейки по сравнению с парсингом картинок\моделек\конфигов и самим чтением с флеша, а тем более HDD.
                      Ответить
                      • > И слазить в жабу по JNI, не забываем.
                        Можно на старте на уровне приложения закешировать. Положить всё нужное в какую-нибудь глобальную структурку Game и юзать всюду без синхронизации и лишних аллокаций.
                        Ответить
                        • > Положить всё нужное в какую-нибудь глобальную структурку Game и юзать всюду без синхронизации и лишних аллокаций.
                          Вот кстати да. Поэтому я и не люблю ленивые синглтоны - с ними ёбли больше, чем профита.
                          Ответить
                          • > Поэтому я и не люблю ленивые синглтоны - с ними ёбли больше, чем профита.

                            So true...
                            Ответить
                          • >Поэтому я и не люблю ленивые синглтоны - с ними ёбли больше, чем профита.
                            +1
                            Их даже в жабах с многопоточными примитивами и моделью памяти из коробки, не сильно тривиально написать, а в крестах старого стандарта и вовсе можно повеситься.
                            Ответить
                        • На старте JNI шное говно вызывать нельзя, потому что appState->activity->clazz ещё не готов, ага, я как-то долго думал, почему прога виснет, хотя ещё не успела войти в мейн.
                          Ах да, у ведра в случае исключения прога тупо виснет.
                          Ответить
                          • говоря на старте я имел в виду внутри main().
                            int main()
                            {
                                InitGlobalGame();
                                // ...
                                DestroyGlobalGame();
                            }
                            Ответить
                            • А вот не факт, что там вообще есть main(). А если есть - Activity в нем, скорее всего, еще не созданы (о чем и пишет Тарас).
                              Ответить
                              • Ну какая-то точка входа в приложение всяко есть. Я не спец в андроидах, но ведь по любому у активити инициализация бывает.
                                Ответить
                                • Ну с жабьей стороны можно было зарегать потомка Application'а, который создается перед любым Activity/Service/BroadcastReceiver (которых ведро запиливает и выпиливает по своему разумению). Как со стороны NDK - х.з.
                                  Ответить
                            • А, ну так тоже можно. Но так ленивее, меньше херни дёргается когда не требуется.
                              Ответить
                    • >Кмк, проще быстренько аллоцировать строку, чем постоянно платить за синхронизацию.
                      Вот кстати да.
                      Но в целом вопрос спорный. "постоянно платить" - это не факт. С одной стороны синхронизация без contentionа может оказаться почти бесплатной (по цене CAS), благодаря всяким хитрым lock elision.
                      C другой стороны хватать мьютекс явно дороже чем алоцировать строчку.
                      Ответить
                      • > дороже чем алоцировать строчку
                        Но ведь аллоцирование строчки тоже содержит в себе атомарные операции...
                        Ответить
                        • Атомарные еще ничего - это быстро. Уход в kernel ради паршивой строчки - вот что накладно.
                          Вообще в ОС обычно есть разные типы мьютексов, легковесные работают очень быстро, при частом однопоточном доступе, но тупят когда сильная конкуренция, и наоборот.
                          Ответить
                      • А если поюзать атомарный указатель? Чтение с aquire (а тем более consume) семантикой дешевое (на интеле, емнип, вообще бесплатное). А записывать в него CAS'ом, освобождая буфер, если там оказался не NULL - в худшем случае джва раза обратится к JNI (от этого можно подстраховаться, но лениво). Как-то так:
                        std::atomic<const char *> directory;
                        
                        const char *GetDirectory() {
                            // может быть уже есть?
                            const char *d = directory.load(std::memory_order_consume);
                            if (d)
                                return d;
                            // нету, выпрашиваем значение у JNI
                            // ...
                            // и сохраняем
                            if (d.compare_exchange_strong(NULL, std::memory_order_release))
                                return d;
                            // соседний поток сохранил до нас
                            free(d);
                            return directory.load(std::memory_order_consume);
                        }
                        Ответить
                        • Без лочки тут похоже есть недостаток.
                          На старте может так случиться что джва и более раз запросим у JNI.
                          Ответить
                          • > похоже есть недостаток
                            Ну так я о нем выше написал ;)

                            А лочку можно прикрутить уже после directory.load(). И на всех проходах кроме первого она не скажется.
                            Ответить
                            • А блин, я только заметил.
                              >И на всех проходах кроме первого она не скажется.
                              Что-то это мне напоминает... А точно! Это ведь похоже на ленивый синглтон!
                              Опять возвращаемся к:
                              Поэтому я и не люблю ленивые синглтоны - с ними ёбли больше, чем профита.
                              Ответить
                              • Может std::call_once подойдет? Оно вроде бы именно это и делает.
                                Ответить
                                • Вау. Не знал такого.
                                  По сути это такой ленивый Future.
                                  Только эти новые фишки всё-равно Тарасу не подойдут, он же пишет под старый стандарт.
                                  Ответить
                              • std::once_flag flag;
                                const char *directory;
                                
                                void RealGetDirectory() {
                                    // выпрашиваем строку у JNI и суем ее в directory
                                }
                                
                                const char * GetDirectory() {
                                    std::call_once(flag, &RealGetDirectory);
                                    return directory;
                                }
                                Ответить
                                • а этот каллванс внутри не на мутексе? или на атомариках хитрожопо сделан?
                                  Ответить
                                  • >а этот каллванс внутри не на мутексе?
                                    А ты как думал? Просто профит в том что это реализовали 1 раз и правильно.

                                    Вообще полагаю там double checked locking, типа как у борманда, только с мьютексом.
                                    1. атомарная проверка
                                    2. мьютекс
                                    3. выполнение метода.
                                    4. освободить мьютекс
                                    То есть мьютекс предотвращает множественные вызовы на инициализации, но когда пройдет первая инициализация, то до лочки код доходить уже не будет.

                                    Плюс сами мьютексы обычно тоже сделаны на атомиках, при отсутствии конкуренции и waitов.
                                    Ответить
                                  • На атомиках гарантированно только атомик_флаг, а колл_уанс может быть и на мютексе. Когда я последний раз глядел в бустовый, он был на атомиках. Или я опять нихуя не понял и добрые местные обитатели меня какашками закидают поправят и литературу посоветуют :)
                                    Ответить
                                    • > колл_уанс может быть и на мютексе
                                      Может. Но если она мютексе, то, скорее всего, на этой архитектуре по-другому его уже никак не запилить. Или авторы компилятора - ленивые обезьяны. Имхо.
                                      Ответить
                    • для потока просто мьютекс на кишки функции
                      Ответить
                    • Ёблю с JNI всё равно лучше делать только один раз.
                      Ответить
    • У бунтарчика багор, не обращайте внимания.
      http://www.gamedev.ru/projects/forum/?id=193863&page=9#m128
      Ещё он говорит что я должен использовать указатели, иначе зачем мне С++.
      Алсо поздравьте с первым местом
      http://www.gamedev.ru/flame/forum/?id=193145
      Ответить
      • Прочитал как:
        >Ещё он говорит что я не должен использовать указатели, иначе мне С++.

        >Алсо поздравьте с первым местом
        Малаца. А хуле, народ не проведешь...
        Хотя я кроме Хулиона не видел игр других участников, но игрушка - зачётный олдскул.
        Ответить
        • Это другой Хулион. Теперь двумерный.
          Другие игры можно по видео прикинуть: http://www.gamedev.ru/flame/forum/?id=135442&page=9#m132
          Ответить
          • А... Я уже понял.
            А оно когда генерирует уровень, как проверяет что его можно пройти?
            Ну что ключи и двери будут открываться по мере прохождения?
            PS> Мне понравилось когда стреляешь, как мясо разлетается...
            Ответить
            • Оно не проверяет, что его можно пройти. Оно сразу так генерирует.
              Я собираюсь статью написать, но мне пока влом.
              Ждите обновления в теме проекта.
              Ответить
              • Это метод DoLoop делает?
                // поиск1: в сторону пустой ячейки
                // поиск2: вылезти в место, где были ранее
                // злоупотреблять не стоит, а то одни двери везде будут, лол
                // поиск3: поход против шерсти
                // поиск4: куда попало
                Ответить
                • Он генерирует круговой маршрут. Вся топология - это совокупность нескольких круговых маршрутов.
                  Ответить
              • Ну как, написал?
                Ответить
      • нет корованов
        Ответить
    • Спасибо выложившему ГК за бесплатную рекламу.
      Ответить
      • Сорцы, если кому.
        http://www.gamedev.ru/files/?id=101336
        И комментарии экспертов
        Ты не тот язык программирования выбрал, тебе надо писать на C#.
        Какая-то странная помесь C-стиля, STL и BOOST. Сквозь весь проект висит std::stringstream, в котором объявлены в том числе и std::string, но многие функции принимают const char *. Указатели почти нигде не используются, управления памятью тоже нет. Есть классы, но рядом мы видим глобальные функции, принимающие указатель на структуру.
        В общем, стиля нет. Либо ты каждый кусок кода рождаешь путём экспериментов со средствами языка, либо разные части кода писали разные люди, либо разные части кода написаны в разом психическом состоянии...
        Ответить
        • Так это и есть тот самый ыксперт
          Ответить
          • Да я понял :)
            Указатели почти нигде не используются, управления памятью тоже нет.
            Есть классы, но рядом мы видим глобальные функции, принимающие указатель на структуру.

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

            Но стиль действительно необычный. Если любовь к struct я понять еще могу (сам часто пишу бубличные поля), но вот почему весь код в h-файлах?
            Ответить
            • > Он так говорит будто указатели и ручное управление памятью - что-то хорошее.

              Именно это он и имеет в виду. А ты думал, что школоло-кулхацкеры - это я преувеличивал?

              > но вот почему весь код в h-файлах

              ну меня с дельфей заебало писать заголовки методов по два раза, думать, что выносить наружу, что не выносить
              и первый мой опыт на крестах обломал меня с шаблонами в cpp и с глобальными переменными, поэтому я cpp недолюблюваю
              Ответить
            • > но вот почему весь код в h-файлах
              Бустянка.
              Ответить
              • Я подумал это тяжкое наследие VC++.
                Плюс Тарас же ненавидит буст, и что главное в Аде такое разделение практикуется повсеместно.
                http://www.adahome.com/ammo/cpp2ada.html#2
                Any Ada package on the other hand consists of two parts, the specification (header) and body (code). The specification however is a completely stand alone entity which can be compiled on its own and so must include specifications from other packages to do so. An Ada package body at compile time must refer to its package specification to ensure legal declarations, but in many Ada environments it would look up a compiled version of the specification.

                Имхо, писать побольше всякого кода (желательно шаблонных рекурсивных compile-time 'оптимизаций') в h-файлы - лучший способ замедлить компиляцию юзерам этих самых h-файлов.
                Ответить
      • switch (rnd.random(6))
        			{
        			case 0 : color = tbal::COLOR_RED; break;
        			case 1 : color = tbal::COLOR_GREEN; break;
        			case 2 : color = tbal::COLOR_YELLOW; break;
        			case 3 : color = tbal::COLOR_TEAL; break;
        			case 4 : color = tbal::COLOR_BLUE; break;
        			case 5 : color = tbal::COLOR_PURPLE; break;
        			}

        Это дизеринг? А чо свищ, а не массив, например?
        Ответить
        • Это не дизеринг, это огоньки для лавовой пещеры. В данном случае массив был бы в тему, да. Но массив не поможет, если я захочу случайный цвет, у которого одна компонента 0, другая 255, а третья случайная, вот и выработалась у меня говнопривычка случайный цвет брать свичом.
          Ответить
        • enum же
          Ответить
    • Я написал портянку про генереции вореций:
      http://www.gamedev.ru/projects/forum/?id=193863&page=10#m143
      Ответить
      • > про генереции вореций
        Эх. Завлекает...
        Не ведитесь, там нет ничего о генераторе бреда, только гейдев.
        Ответить

    Добавить комментарий