Ответы пользователя по тегу C
  • Как именно гарантируется выделения n байт памяти библиотекой stdint.h?

    @Mercury13
    Программист на «си с крестами» и не только
    В моей версии MinGW это сделано так.
    На уровне компилятора определяется макрос __INT64_TYPE__, который на данных настройках значит long long. Затем через жёсткую препроцессорную магию определяются и остальные типы и константы, связанные с int64.

    # include_next <stdint.h>  // то есть самого себя!
    
    . . .
    
    #ifdef __INT64_TYPE__
    # ifndef __int8_t_defined /* glibc sys/types.h also defines int64_t*/
    typedef __INT64_TYPE__ int64_t;
    # endif /* __int8_t_defined */
    typedef __UINT64_TYPE__ uint64_t;
    # undef __int_least64_t
    # define __int_least64_t int64_t
    # undef __uint_least64_t
    # define __uint_least64_t uint64_t
    # undef __int_least32_t
    # define __int_least32_t int64_t
    # undef __uint_least32_t
    # define __uint_least32_t uint64_t
    # undef __int_least16_t
    # define __int_least16_t int64_t
    # undef __uint_least16_t
    # define __uint_least16_t uint64_t
    # undef __int_least8_t
    # define __int_least8_t int64_t
    # undef __uint_least8_t
    # define __uint_least8_t uint64_t
    #endif /* __INT64_TYPE__ */


    Полагаю, это связано с тем, что компилятор и библиотека сильно кроссплатформенны и написаны в большом отрыве друг от друга. Решай мы более простую задачу — то есть связку «компилятор-библиотека» для конкретной ОС — можно было просто
    typedef long long int64_t;
    Ответ написан
    Комментировать
  • В какого типа переменных хранить адреса?

    @Mercury13
    Программист на «си с крестами» и не только
    Вариантов много.
    1. Непрозрачные указатели, которые нельзя разыменовывать.
    struct OpaqueAddress;
    using Address = OpaqueAddress*;

    2. void*, const void*.
    3. uintptr_t.
    4. Жёсткие int’ы, если работаем с конкретной посторонней прогой под конкретную архитектуру (например, пишем чит к игре).
    using Address = uint32_t;
    5. Enum class, основанный на соответствующем int’е.
    enum class Address : uintptr_t { NUL = 0 };
    Ответ написан
    Комментировать
  • Что стоит учить с или c++ или c#?

    @Mercury13
    Программист на «си с крестами» и не только
    Если ты умеешь программировать на чём угодно, хоть на Скрэтче — можно любой по желанию. Разберёшься.
    Если с нуля — только C#, на нём меньше шансов напортачить. Главная проблема Си, плохо решённая в Си++,— для простых вещей приходится работать со сложными концепциями вроде указателей для scanf.
    Ответ написан
    Комментировать
  • Почему '\xDA', '\xc4', и другие управляющие последовательности не работают?

    @Mercury13
    Программист на «си с крестами» и не только
    Потому что вы перевели консоль в кодировку Win-1251 и и ждёте в ней псевдографику.
    Лучше всего работать в каком-то из вариантов Юникода.
    Ответ написан
    Комментировать
  • Что и почему лучше подключить в C++? math.h или cmath? stdio.h или cstdio?

    @Mercury13
    Программист на «си с крестами» и не только
    math.h — это для кода, который должен быть одновременно Си и Си++. Также разглючка в некоторых версиях Embarcadero.
    cmath — рекомендуется в Си++.
    Ответ написан
    Комментировать
  • В чём ошибка вычисления бесконечно убывающей прогрессии с точностью до эпсилон?

    @Mercury13
    Программист на «си с крестами» и не только
    У вас две ошибки.
    1. Целочисленное переполнение в знаменателе в prog += (double)1/(i*(i+1));. Вычисляйте знаменатель в double, его длины более чем хватит.
    2. Нельзя брать в счёт аналитический результат, надо высчитывать погрешность по имеющимся данным. Поскольку ещё и ряд сходится ХУдожественно — придётся помучиться, 1/(i(i+1)) < k·eps не катит (катит, если элемент ряда убывает экспоненциально, а у нас всего лишь квадратично).
    Ответ написан
    Комментировать
  • Почему поведение fscanf ( stdin, "%c", &c ) различается при чтении EOF в msvc и gcc?

    @Mercury13
    Программист на «си с крестами» и не только
    В таком случае, когда есть подозрение на конец файла, надо проверять возвращаемое значение функции fscanf:
    • EOF — файл закончился;
    • 0 — не прочитали (в данном случае, когда читаем символ такого явно не будет);
    • 1 — прочитали.

    Если во втором случае MSVC возвращает EOF, но заполняет — поведение некузявое, но допустимое.
    Если во втором случае MSVC возвращает 1 — поведение дрянь.

    Вообще для такого дела fscanf — большая пушка, лучше использовать fgetc.
    Ответ написан
  • Что по смыслу делает этот if?

    @Mercury13
    Программист на «си с крестами» и не только
    Мы тут считываем текстовый файл, и разбор файла по строкам происходит так.
    1. Считываем кусок файла.
    2. Находим \n, если он не в начале буфера.
    3. Движемся назад, пока не увидим начало буфера или другой \n. Отсюль досюль — это строка текстового файла.
    4. Если при компиляции включено FIND_LIB_NAME — пропусти четыре поля, отделённых пробелом, затем кучу пробелов (один или больше), и всё, что до \n — это имя библиотеки. Прямо в буфере заменяем символ конца строки текстового файла на символ конца Си-строки, обрабатываем и возвращаем как было.

    Вообще мне этот исходник не нравится — он сильно завязан на устройство конкретного MAP-файла.
    1. Работа с offset подозрительная — то ли длина каждой строчки является степенью двойки и всё оказывается в порядке, то ли не понимаю что.
    По идее, должны быть два дополнительных блока: 1) если мы упёрлись в конец и хоть один \n считали — всё, что осталось, мы переносим в начало буфера и продолжаем считывание дальше. 2) Если ни одного \n не считали — файл совсем уж странный, слишком длинная строка, выкидываем ошибку.
    2. Если попадёт файл, чей формат совсем другой, система, конечно, не вылезет за пределы буфера, но будет читать непонятно что непонятно откуда. Границами чтения должно быть «отсюль досюль», а не весь буфер. Попытался выяснить, откуда взялся файл — не удивительно, что последний его коммит — это тупая защита от чтения за пределами буфера.
    Ответ написан
    Комментировать
  • Кроссплатформенное программирование на C?

    @Mercury13
    Программист на «си с крестами» и не только
    Что тогда делают? Есть три варианта.
    1. Перейти на кроссплатформенную библиотеку (Qt, например).
    2. Наладить свою небольшую библиотеку, которая когда-нибудь станет кроссплатформенной.
    3. Наладить механизм псевдонимов.
    Эти варианты можно объединять: что-то перевести на другую библиотеку, что-то написать своё.

    Примеры из личного кода.

    ОДИН. Ну, например, собственные механизмы работы с путями к файлам потихоньку уходят в сторону std::filesystem::path.

    ДВА. Ну, допустим, сделал свою библиотеку доступа к файлам — бонуса три. 1) Есть функции вроде writeIW (Intel word). 2) Проще писать свои абстрактные потоки, чем с std. 3) Феноменальная скорость под Windows (использует нечастый, но быстрый overlapped API с двойной буферизацией). На остальных ОС — обычная обёртка над FILE*.

    ТРИ. std::random_device из MinGW 9 давал детерминированную последовательность. Потом попытался бросить эту обходную ветку, но кто-то из программистов пожаловался — на его машине r_d просто не инициализировался.
    #if defined(__MINGW32__) && defined(__GNUC__) && !defined(__clang__)
    
        #define MINGW_RANDOM_DEVICE_WORKAROUND
    
        class MingwRandomDevice {};  // куча WinApi, опустим
    #else //defined(__MINGW32__) && defined(__GNUC__) && !defined(__clang__)
        #include <random>
        using MingwRandomDevice = std::random_device;
    #endif //defined(__MINGW32__) && defined(__GNUC__) && !defined(__clang__)
    Ответ написан
    1 комментарий
  • Как в языке си вернуть пустой массив?

    @Mercury13
    Программист на «си с крестами» и не только
    Если возвращаем НОВУЮ память:
    1. вернуть массив заведомо бóльшей длины и поле длины.
    struct Arr1 {
      int length;
      int data[100];
    }
    struct Arr1 someFunc() {}

    2. Вернуть динамический массив.
    struct Arr2 {
      int length;
      int* data;   // не забудь free(arr.data);
    }
    struct Arr2 someFunc() {}


    Если работаем в ИМЕЮЩЕЙСЯ памяти.
    3. Тот же Arr2, но разница в том, что data не надо высвобождать.
    4. А если arr.data гарантированно понятное — то можно вернуть только int, поле длины (как в функции sprintf).
    int sprintf ( char * str, const char * format, ... );

    Она и возвращает массив — только его адрес гарантированно будет str и вернуть надо только длину.
    Ответ написан
    Комментировать
  • Почему у меня %c выводит а %s выбивает ошибку?

    @Mercury13
    Программист на «си с крестами» и не только
    char x = 'FIO';
    Ошибка.
    Многосимвольные константы определяются реализацией, используются для жёсткой оптимизации, и в Visual C++ крайне ненадёжны. В GCC вроде бэ-мэ, но в любом случае преобразование в char 'FIO', которое должно занимать три байта, даст 'F' или 'O'.
    Вам нужна не символьная константа, а строковая:
    char x[] = "FIO";
    И, соответственно, %s.

    А как действует printf и любая другая функция с переменным числом аргументов? Эти аргументы сваливаются навалом в область памяти, которая называется «стек вызовов», и printf начинает этот навал разбирать. Чтобы сдвинуло указатель на один байт и сказало: это char — и используется формат %c. Разумеется, неверный формат приведёт к неверной интерпретации типа данных и отказу на одной или всех платформах.
    %c — в памяти лежит один байт, интерпретировать его как один символ
    %s — в памяти лежит указатель на строку, заканчивающуюся нулём, 4/8 байтов. (Все массивы в исходном Си куда бы то ни было передаются как указатели.)
    Ответ написан
    3 комментария
  • Как сделать такой финт ушами с 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 комментарий
  • Почему char - 1 байт, а символьный литерал ('A') - 4?

    @Mercury13
    Программист на «си с крестами» и не только
    А теперь скажу правильный ответ.
    В Си символьный литерал имеет тип int и потому его sizeof 4 байта.
    В Си++ у него тип char и 1 байт. Потому те, кто создавал CPP-файл, проблемы не видели. Очевидно, связано с перегрузкой функций: как-то не хочется, чтобы в foo('A') вызывалась версия для int.
    #include <stdio.h>
    
    int main()
    {
        int sz = sizeof('A');  // латинское
        printf("sz = %d\n", sz);
        return 0;
    }

    Си: 4
    Си++: 1

    При написании char test='A' на стеке будет 1 байт (+выравнивание). Здесь Си, грубо говоря, проводит преобразование типа — прямо при компиляции. Если написать char test=L'Й', сообщит, что преобразование при компиляции ushort→char обрежет результат с 1049 до 25.
    Ответ написан
    Комментировать
  • Как запустить код Си в проекте С++?

    @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.

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

    @Mercury13
    Программист на «си с крестами» и не только
    Связано это в первую очередь с механизмом передачи переменных аргументов в функцию Си.
    Они просто сваливаются в стеке один за другим — и нужно каким-то образом показать, сколько этих аргументов и каких типов. И уж во вторую очередь — формат вывода: точность, система счисления, заглавные буквы, ширина и прочее.

    Именно по этой причине — спецификаторы printf в первую очередь задают типы аргументов — функция printf ничего не может сделать, если мы ошиблись с типом. Потому что %d (%ld, %lld) для неё в первую очередь способ понять, сколько байтов взять со стека и как их интерпретировать.

    Си++ для этих целей использует перегрузку функций, Delphi — тэгированные данные, Java — объектный полиморфизм. Так что они знают, с каким типом данных имеют дело — и для них не нужно различать signed/unsigned int/long/long long. Для них %d/%x/%X означает разные варианты целого, %e/f/g — разные варианты дробного, и. т.д. Если для дробного пользователь напишет %d, они или выкинут ошибку, или как-то интерпретируют.
    Ответ написан
    Комментировать
  • Что за функция swow?

    @Mercury13
    Программист на «си с крестами» и не только
    Это чья-то собственная. Вы её и не найдёте. Особенно вместе с общими названиями generate и foo.
    Шерстите свой исходник. А если он не полный — вы сами себе злобный буратино.
    Ответ написан
    Комментировать
  • Почему при выводе элемента из перечисления выводит не нужное мне значение?

    @Mercury13
    Программист на «си с крестами» и не только
    Полагаю, дело в системе сборки — нечто подобное я испытывал и в Doom (старом, 1994 года), где makefile редактировался руками.

    Например, у вас было
    typedef enum Buttons{
        SAVE,
        OPEN,
        SORT,
        EXIT
    }Buttons;

    После того, как вы откорректировали enum, один файл перекомпилировался, другой нет — отсюда такой артефакт.
    Ответ написан
  • Почему при вводе текста добавляются лишние символы?

    @Mercury13
    Программист на «си с крестами» и не только
    Я сделал вот такой код (простите, для простоты «с крестами»).
    #include <stdio.h>
    #include <iostream>
    
    int main() {
        int a = getchar();
    
        while (a != '\n') {
            std::cout << a << "-" << static_cast<char>(a) << std::endl;
            a = getchar();
        }
    }
    
    143-П
    224-р
    168-и
    162-в
    165-е
    226-т

    Так что не здесь полом. Но учтите, что работа была под виндой, а значит, в кодовой странице DOS-866. Подкиньте ОС, рабочую кодировку и чуть больше кода обвязки.

    UPD1. Если символов 12 вместо 6 и ОС Android — перед нами кодировка UTF-8. И выводить в консоль по одному символу не очень кузяво, поскольку для русского текста получаются неполные кодовые позиции. Закройте буфер нулём и выведите целиком.
    Ответ написан
  • Почему прошивки пишут на С?

    @Mercury13
    Программист на «си с крестами» и не только
    Потому что низкоуровневый софт должен…
    • Быстро выполняться. Потому что его выполняют или в глубоких циклах (например, ОС), или на слабом железе.
    • Расходовать мало памяти и не «течь». Потому что его часто выполняют на слабом железе. Или в чужих стеках, как драйвер.
    • Содержать мало зависимостей. Если мы зависим от большой библиотеки вроде Qt, а её реализации на данной машине нет — выкуси. Точно так же интерпретатор Питона может оказаться лишней зависимостью. И многопоточка, которая часто требуется для «мусорщика».
    • Быть совместимым с кодом на других языках. Это касается системного и драйверного кода, который вызывают из прикладного ПО (или, наоборот, прикладной кода из системной проги) — и даже с кодом на разных версиях .NET в расширениях оболочки Windows есть вопрос.
    • И в то же время требуется некая доля переносимости и абстракции. Например, мы пишем джойстик на AtMega и не хотим мучиться с длинными числами — ЯВУ лучше будет, чем ассемблер. 10 бит АЦП на 8-битном процессоре уже длинное число!!

    Почему Си? У него есть две фишки: большое поле для ручной оптимизации (ключевое слово register, op++), и он полагается на две ассемблерных утилиты — линкер и библиотекарь (tlink и tlib, например). Из-за этого компилятор Си довольно просто написать под новую машину, и на НЕоптимизирующем компиляторе можно писать довольно быстрый код.
    Ответ написан
    Комментировать
  • Что не так в Eclipse IDE?

    @Mercury13
    Программист на «си с крестами» и не только
    printf("%c", x);
    Форматную строку упустил.
    Ответ написан
    1 комментарий