Ответы пользователя по тегу C++
  • Header-only. Страдает ли размер бинарника?

    @Mercury13
    Программист на «си с крестами» и не только
    Для шаблонов Си++ в линкеры пришлось добавлять специальное исключение — ведь расшаблоненные функции будут повторяться в каждом имеющемся объектном файле.

    Теста ради сделал такой проект

    // MAIN.CPP
    #include <iostream>
    
    #include "tmpl.h"
    
    void doFile1();
    
    int main()
    {
        const char* x = "main";
        outThing(x);
        doFile1();
        return 0;
    }
    
    // FILE1.CPP
    #include "tmpl.h"
    
    void doFile1()
    {
        const char* x = "doFile1";
        outThing(x);
    }
    
    
    // TMPL.H
    #pragma once
    
    #include <iostream>
    
    template <class T>
    void outThing(const T& x)
    {
        std::cout << "The thing is " << x << std::endl;
    }


    Для чего x — при расшаблонивании появилось outThing(char[5]&) и соответстсвенно char[8]&.
    А теперь дамп линкера
    Discarded input sections
    
     .text$_Z8outThingIPKcEvRKT_
                    0x0000000000000000       0x50 debug/main.o
    
    Linker script and memory map
    
    .text$_Z8outThingIPKcEvRKT_
                    0x0000000140002900       0x50 debug/file1.o
                    0x0000000140002900                void outThing<char const*>(char const* const&)
    
    Ну и ещё парочка структур для раскрутки стека и подстановки адресов…


    Так что даже при -O0 оно не будет ничего дублировать. Да и логикой понятно: что дублировать, что не дублировать — один хрен потребуется специальная логика, и разницы мало.
    Ответ написан
    1 комментарий
  • Почему берутся проблемы с совместимостью массивов и контейнеров?

    @Mercury13
    Программист на «си с крестами» и не только
    Учите матчасть!
    Версия для буфера памяти предполагает, что данные хранятся в непрерывном буфере nm×double (то есть длина mn, элемент double).
    Но vector<vector> — он НЕ непрерывный буфер.
    Есть непрерывный буфер n×vector<double> — центральный вектор. И каждая из n строчек по отдельности — непрерывный буфер m×double. Относительно друг друга в памяти они могут располагаться как угодно.

    Как исправить? — проще всего
    double s = 0;
    for (i...) {
      s += Sum(m, &a[i][0]);
    }

    Можно через шаблоны работать, но это уже сложнее и я сам не смогу это с листа написать, что уж говорить про первокура.
    Ответ написан
    Комментировать
  • Что такое указатели в С++?

    @Mercury13
    Программист на «си с крестами» и не только
    Если переменных в памяти потребуется слишком большое количество, которое не сможет вместить в себя сама аппаратная часть, произойдет перегрузка системы или её зависание.

    Либо будет дичайше свопить, либо отказ с нехваткой памяти. А уж как этот отказ устроен — через «падение», зависание, какое-то сообщение… — это может быть по-разному.

    Можете себе представить, если бы небезызвестная Battlefield 3 использовала такой метод работы с данными? В таком случае, самым заядлым геймерам пришлось бы перезагружать свои высоконагруженные системы кнопкой reset после нескольких секунд работы игры.

    Нет. Памяти было бы отведено с запасом уже при запуске, и даже небольшая игра требовала бы многие и многие гигабайты памяти, а Battlefield бы просто не запускался даже на топовых ПК. И часть настроек качества не имела бы смысла — изначально, уже при загрузке EXE’шника, выделено памяти с запасом и никак не ужмёшься. Но нет худа без добра — половины глюков и вылетов не существовало бы. Так что мелкие игры, способные бегать на 99% современных и не очень ПК и потому не имеющие настроек качества, этим механизмом вполне пользуются.

    Разрешите попиариться — пишу программу «Юникодия», энциклопедию символов — таблица на 140 тыс. позиций (количество существующих на данный момент символов) занимает большую часть EXE’шника. А таблица на 1,1 млн (ёмкость Юникода) восьмибайтовых указателей тоже выделена статически, но места в EXE’шнике не занимает и заполняется на старте. Таким образом мы может легко пробежаться по всем символам, и можем по коду символа найти его запись.

    Если не уничтожать неиспользуемые объекты, очень скоро они заполнят весь объем ресурсов ПК.

    Указатели — это не только про уничтожение памяти, но и про выделение.

    Но главное, что вы поняли одно: указатель — это адрес в памяти плюс вкомпилированный в программу тип (на железном уровне типов нет).

    И второе: по указателю мы можем получить доступ к объекту, чьего имени мы не знаем (как у меня в таблице символов) или он безымянный (если он выделен динамически, при работе программы, а не вкомпилирован в неё).
    Ответ написан
  • Как сделать такой финт ушами с double?

    @Mercury13
    Программист на «си с крестами» и не только
    Почему нельзя? Пишу на Си с крестами, но там ничего специфичного нет, кроме парочки перестраховок.
    В Си++20 появился ещё и bit_cast.

    #include <iostream>
    
    union DoubleInt {
        double asDouble;
        uint64_t asInt;
    };
    
    static_assert(sizeof(double) == sizeof(uint64_t), "Strange machine with double != int64");
    
    constexpr int BITS_MANTISSA = 52;
    constexpr int BITS_EXPONENT = 11;
    constexpr int BITS_SIGN = 1;
    
    static_assert(BITS_MANTISSA + BITS_EXPONENT + BITS_SIGN == 64, "Programmer's funkup");
    
    constexpr uint64_t MANTISSA_UNIT = uint64_t(1) << BITS_MANTISSA;
    constexpr uint64_t MANTISSA_MASK = MANTISSA_UNIT - 1;
    
    constexpr int EXPONENT_SHIFT = BITS_MANTISSA;
    constexpr uint64_t EXPONENT_MAX = (uint64_t(1) << BITS_EXPONENT) - 1;
    constexpr uint64_t EXPONENT_ORIGIN = EXPONENT_MAX >> 1;
    constexpr uint64_t EXPONENT_MASK = EXPONENT_MAX << EXPONENT_SHIFT;
    constexpr uint64_t EXPONENT_SHIFTED_ORIGIN = EXPONENT_ORIGIN << EXPONENT_SHIFT;
    
    constexpr int SIGN_SHIFT = BITS_MANTISSA + BITS_EXPONENT;
    constexpr uint64_t SIGN_MASK = uint64_t(1) << SIGN_SHIFT;
    
    int main()
    {
        DoubleInt x { -3.45 };
    
        // Простите уж, без денормализованных чисел
    
        // Оставим знак и мантиссу
        DoubleInt xMantissa = x;
        xMantissa.asInt &= (MANTISSA_MASK | SIGN_MASK);
        // И добавим туда стандартный нулевой порядок
        xMantissa.asInt |= EXPONENT_SHIFTED_ORIGIN;
    
        // Извлечём порядок
        int exponent = ((x.asInt & EXPONENT_MASK) >> EXPONENT_SHIFT) - EXPONENT_ORIGIN;
    
        std::cout << xMantissa.asDouble << "*2^" << exponent << std::endl;
    
        return 0;
    }
    Ответ написан
    1 комментарий
  • Почему после вызова деструктора unordered_map перестаёт работать?

    @Mercury13
    Программист на «си с крестами» и не только
    Как в анекдоте (явно времён 70-х, когда между Гондурасом и Сальвадором была Футбольная война).
    — Что-то меня беспокоит Гондурас.
    — Так не чеши его.

    Вызов деструктора каскадно вызовет деструкторы всех полей, и это приведёт к тому, что на месте u_m будет мусор вроде nullptr и висячих указателей.

    Автодеструкторы — важнейшая фишка Си++, отличающая его от других языков, и явный вызов деструктора нужен крайне редко, когда мы хотим чуть более ручное управление памятью. Вот пример из нашего проекта. Есть выделенный кусок памяти, и мы хотим уничтожить там объект и на его месте создать новый, не отдавая-выделяя память. Считаем, что объекты одинаковые, иначе вся магия пропадёт.
    wiTile->~WiTile();
    new (wiTile) WiTile(client(), icons, clazz, tileSettings(i));

    В проекте на сотни тысяч строк я нашёл 34 таких места в библиотеках, в основном RapidJson, и три места собственно в программе.

    Если хотите полностью очистить — делайте отдельную функцию clear.

    Кстати, в вашем случае явно прописанный деструктор не нужен вообще. Вызываемые автоматически деструкторы полей сделают всё, что надо, чтобы корректно уничтожить его.
    Ответ написан
    Комментировать
  • Как передать шаблонную функцию в параметры функции?

    @Mercury13
    Программист на «си с крестами» и не только
    testFuncTemplate(a, b, func<T>)
    testFuncTemplate(a, b, func<decltype(a)>)
    Ответ написан
    Комментировать
  • Как вызвать CreateThread внутри Form?

    @Mercury13
    Программист на «си с крестами» и не только
    Tony сказал правильно, но есть ещё одна вещь.
    CreateThread использовать ЗАПРЕЩАЕТСЯ. Точнее, можно — но для этого нужно знать, что делаешь. А так для прикладного прогера правило — надо перевести библиотеку времени выполнения в многопоточный режим, и для этого вызвать аналог из этой самой библиотеки. В Си++ для этого используется функция beginthreadex(), в Delphi — BeginThread.
    Ответ написан
    Комментировать
  • Можно ли сделать код, который, если constexpr-выражение true, выдаст предупреждение?

    @Mercury13 Автор вопроса
    Программист на «си с крестами» и не только
    Придумал.

    [[maybe_unused]] byte debugWarning = SIMULATE_CHANGED_HORIZON * 1000;


    В случае false компиляция пройдёт гладко. В случае true преобразование 1000 → byte выдаст предупреждение.
    Ответ написан
    Комментировать
  • Как запустить код Си в проекте С++?

    @Mercury13
    Программист на «си с крестами» и не только
    не удается преобразовать 'char*' в 'int*' для аргумента '1' в 'int

    Как правило, ошибка. Функция хотела указатель на 4-байтовые слова — а мы передаём ей указатель на байты.
    Если очень нужно рассматривать int* как char* (например, работаем не с данными какого-то определённого типа, а просто с байтами в памяти) — используйте reinterpret_cast.

    недопустимое преобразование из 'int*' в 'int'

    Почти всегда ошибка. Функция хотела число — а мы ей передаём ей указатель.
    Если реально по какой-то причине нужно значение указателя воспринимать как число — используйте reinterpret_cast.

    Возможен и другой вариант — вы просто упустили операцию «разыменовать» *.

    warning: narrowing conversion of '143' from 'int' to 'char' inside

    Точно ошибка — 143 не вписывается в char (−128…127). Если очень надо, используй static_cast.

    Покажите лучше код, и я устрою ему ревизию.
    Ответ написан
    Комментировать
  • Почему нельзя возвращать объект по значению в non-const &?

    @Mercury13
    Программист на «си с крестами» и не только
    const struct s & ref = f();
    Этот механизм называется «продление жизни временного объекта».
    Можно написать вот так.
    struct s && ref = f();

    Главная причина преобразования временного объекта только в const T& и T&& — тонкие ошибки при суперпозиции функций.
    А именно — временный объект мы ни в коем случае не должны передать в функцию, принимающую s&.
    void modify(s& arg) { ++arg[1]; }
    
    ...
    
    modify(f());

    Мы вызвали функцию f(), её результат передали в modify, и… похѣрили.
    Ответ написан
    Комментировать
  • Возможно ли автоматическое добавление переменной компилятором в namespace?

    @Mercury13
    Программист на «си с крестами» и не только
    В любом языке можно только так.
    namespace Addresses {
      uintptr_t myAddresses = processManager.xxx;
    }

    Только в CPP-файле; в хедере надо
    namespace Addresses {
      extern uintptr_t myAddresses;
    }

    Но не советую по двум причинам.
    1. Контроль задержек при инициализации программы — дело противное. На всех языках. Например, на Java ME я просил: все длинные операции делать, когда на экране что-то появится.
    2. Вторая причина специфичная для Си++. Порядок инициализации разных модулей не определён. Если менеджер в одном модуле, а адреса в другом, адреса могут с вероятностью 50/50 инициализироваться, когда менеджера ещё нет.
    Ответ написан
    6 комментариев
  • Как заменить одинарный обратный слеш?

    @Mercury13
    Программист на «си с крестами» и не только
    Не сильно оптимизированный, но действующий код.
    CONST_STR = string_view или const string& в зависимости от версии Си.
    На выходе — кол-во замен.
    size_t str::replace(
            std::string& aString,
            const char aWhat,
            const char aByWhat)
    {
        size_t r = 0;
        FOR_S(i, 0, aString.length())
            if (aString[i] == aWhat) {
                aString[i] = aByWhat;
                ++r;
            }
        return r;
    }
    
    size_t str::replace(
            std::string &aString,
            char aWhat,
            CONST_STR aByWhat)
    {
        if (aByWhat.length()==1)
        {   // Simple replace
            return str::replace(aString, aWhat, aByWhat[0]);
        }
        // More complex replace
        static const size_t szWhat = 1;
        const size_t szByWhat = aByWhat.length();
        size_t p = 0, r = 0;
        while ((p = aString.find(aWhat, p)) != std::string::npos)
        {
            aString.replace(p, szWhat, aByWhat);
            p += szByWhat;
            ++r;
        }
        return r;
    }
    Ответ написан
    Комментировать
  • Где хранится nullptr? Для разных программ она разная?

    @Mercury13
    Программист на «си с крестами» и не только
    Что значит «хранится»?
    • Значение nullptr хранится в переменной-указателе — как и любое &someObject.
    • Объекта правильной «объектной» формы (если, конечно, у объекта присутствует какой-то инвариант или хотя бы таблица виртуальных методов) по адресу *nullptr нет; в большинстве ОС разыменование nullptr приводит к нарушению сегментации и мгновенному вылету (но не обязательно — например, DOS).
    • Во всех известных мне ABI (x86/DOS, x86 и x64/Windows, x86 и x64/Linux) nullptr равен нулевому адресу. Это обосновано — по этому адресу обычно располагается таблица прерываний, точка входа, префиксный сегмент или прочая системная хрѣнь, сюда точно соваться не стоит. Но теоретически не обязательно. Этот адрес, естественно, общий для ABI, чтобы можно было объединять в одну программу результаты компиляции разными языками.
    Ответ написан
    Комментировать
  • Возможно ли сделать шаблонную функцию дружественной классу?

    @Mercury13
    Программист на «си с крестами» и не только
    #include <iostream>
    
    class Wrap
    {
    public:
        Wrap(int x) : value(x) {}
    private:
        int value;
    
        template <class T>
        friend void out(const T&);
    };
    
    template <>
    void out(const Wrap& x)
        { std::cout << x.value << std::endl; }
    
    int main()
    {
        Wrap x(42);
        out(x);
        return 0;
    }


    Если нужно в пространстве имён…
    spoiler
    #include <iostream>
    
    namespace qq {
        template <class T>
        void out(const T&);
    }
    
    class Wrap
    {
    public:
        Wrap(int x) : value(x) {}
    private:
        int value;
    
        template <class T>
        friend void qq::out(const T&);
    };
    
    template <>
    void qq::out(const Wrap& x)
        { std::cout << x.value << std::endl; }
    
    int main()
    {
        Wrap x(42);
        qq::out(x);
        return 0;
    }
    Ответ написан
    Комментировать
  • Почему в C++ всё именно так?

    @Mercury13
    Программист на «си с крестами» и не только
    1. Const-корректность придумал Бьярне Строуструп, и только ≈1999 её внесли в Си без крестов. А поскольку память, занятую строковым литералом, изменять запрещается (а в современных ОС литералы сидят в особом неизменяемом сегменте), понятное дело, новым типом для них стал const char*. Поскольку Си99 для совместимости позволял писать char* text = "foo";, а разработка Си(++) застопорилась на десятилетие, учебники по Си очень долго писали такую нерекомендуемую строчку.

    3. Для маленьких объектов это одно и то же, но для больших немалая разница. Эта программа компилируется начиная с Си++17.
    #include <iostream>
    
    class WrapInt
    {
    public:
        int value = 0;
        WrapInt(int x) : value(x) {}
        WrapInt(WrapInt&) = delete;
    private:
    };
    
    int main()
    {
        WrapInt x = 42;
    }


    int zero = 0; до C++14 — создать временный объект, вызвать конструктор перемещения, а остальное делает оптимизатор. С Си++17 — вызов конструктора, если он не explicit. Мне казалось, что в очень старых версиях DJGPP был задействован op=, но ХЕЗ, MinGW и Си++03 вызывают именно конструктор.

    int zero = int(0); — что удивительно, то же самое, только конструктор любой! Хотя выглядит как явное создание временного объекта.

    int zero(0); — вызов конструктора во всех версиях Си++.

    int zero{0}; — универсальный инициализатор Си++11, также вызов конструктора.

    4. int array[] = { 1, 2, 3}; — все версии Си и Си++. int array[] { 1, 2, 3}; — универсальный инициализатор Си++11.

    6. Эдсгер Дейкстра рассказал, почему массивы должны нумероваться с нуля, а целые диапазоны — задаваться в формате [a,b). Итак, у нас есть диапазон [a,b) и массив с запасом. Как сдвинуть b и добавить в диапазон один элемент? — array[b] = newElem; ++b;. Керниган и Ритчи решили объединить это чудо в одну строку array[b++] = newElem;, к тому же в процессорах действительно бывают пред- и постинкрементные операции.

    7. void — это псевдотип, чьи элементы создавать нельзя. Указатели можно передвигать с шагом, который равен размеру типа — то есть прибавляем 1, реально прибавляется 1, 2, 4, 8 или сколько там. Для void*, который является простым указателем на память, шаг 1 байт. Или 1 слово, если машина оперирует более крупными словами — такие были, но из-за переусложнённой работы с текстом от них отказались. Но инкременты-декременты void*, если и возможны, то нежелательны, и вот почему.
    Чтобы «соединить ежа с ужом» (длинное слово и доступ по байтам), x86 говорит: работа с невыровненными данными (например, 4 байта по адресу 21) будет происходить медленно и по неопределённому алгоритму. Itanium вообще, ЕМНИП, к невыровненным данным не обращается, как это устроено в компиляторах: char* → int* говорит, что обращаться нужно медленно и программно, void* → int* — быстро и аппаратно.

    8. libc можно и не подключать, хоть это и сложно. А return из main НЕ вызывает std::exit. Линкер создаёт код обвязки, который открывает все глобальные объекты, вызывает main), закрывает все глобальные объекты и выходит из программы по-системному.
    Ответ написан
    Комментировать
  • Что за скобки в выражении std::true_type{}?

    @Mercury13
    Программист на «си с крестами» и не только
    Универсальный инициализатор Си++11. В нём может быть что угодно: параметры конструктора, initializer_list, поля структуры… Ваш код эквивалентен коду
    std::string helloWorld(std::move(hello().operator std::string()));

    Вроде верно — раньше, НЯЗ, в helloWorld вызывался op= КОНСТРУКТОР КОПИРОВАНИЯ, а сейчас С СИ++17 конструктор.
    Для временных объектов вызывается версия string(string&&), я это обозначил как string(std::move()).
    Ну и для преобразования hello в string используется hello::op string.
    hello(), или, начиная с Си++11, hello{} — это создание временного объекта с помощью конструктора по умлочанию.
    Ответ написан
    Комментировать
  • Как решить ошибку "String is not null terminated"?

    @Mercury13
    Программист на «си с крестами» и не только
    Вы неправильно пользуетесь функцией strcat_s.
    Традиционно в Си (видимо, из-за особенностей машины PDP-11) каждая строчка заканчивается нулевым символом.
    Смысл strcat_s — если строка находится в буфере ограниченной длины и по какой-то причине не нуль-терминированная, функция не полезет в «левую» память, а ограничится размером буфера. Вторым параметром должна быть длина буфера, а не strlen, который обесценивает весь смысл суффикса «safe».
    Ответ написан
    Комментировать
  • Как поделить на о в С++?

    @Mercury13
    Программист на «си с крестами» и не только
    Деление на целочисленный 0 невозможно, происходит авария «деление на 0».

    В процессорах, которые делят 2w:w → w, эта авария происходит и в других случаях. Скажем, если слово — это две десятичных цифры, то 1000:2 даст ошибку: 500 не укладывается в эти две цифры. В Си++, который умеет делить только w/w, такое сделать можно только на ассемблере.

    Деление конечного на дробный 0.0 даёт ±∞ — особое «число», которое больше/меньше любого конечного.
    Деление 0.0/0.0 даёт NaN (not a number, не-число).

    Откуда вы взяли мантиссу 1,87235 — я не знаю. Минимальное денормализованное число в float 1.4e−45, в double 4.9e−324. Почему так мало знаков — да потому что погрешность у таких чисел ±100%: меньше только 0, а следующее вдвое больше.

    UPD. В редко используемом и очень медленном extended (= long double) есть что-то похожее — 1.9e−4951.
    Ответ написан
    Комментировать
  • Как уменьшить выходной exe при компиляции?

    @Mercury13
    Программист на «си с крестами» и не только
    Это вообще как компилируете?
    Если у вас MinGW и консольная программа, советую что-то вроде
    -O3 -Os -static-libgcc -static-libstdc++ -static -lpthread

    Получается самодостаточный EXE’шник, и для небольших программ ≈400K, для программ побольше — около 2М.
    Ответ написан
    Комментировать
  • Что обозначают записи: Class object(&anotherObject); и AnotherClass anotherObject(10);?

    @Mercury13
    Программист на «си с крестами» и не только
    1. Создать в стандартном месте (на стеке или в сегменте данных) объект object типа Class с вызовом конструктора Class::Class(AnotherObject*).
    2. Создать там же объект anotherObject типа AnotherClass с вызовом конструктора AnotherClass::AnotherClass(int).
    Ответ написан
    Комментировать