Ответы пользователя по тегу C
  • Какова адресация элементов структуры в си?

    @Mercury13
    Программист на «си с крестами» и не только
    Да, будет хранить в том порядке, в котором записал. Гуглите понятие POD = Plain old data.
    en.cppreference.com/w/c/language/struct
    Ответ написан
    Комментировать
  • Что такое мультисписок?

    @Mercury13
    Программист на «си с крестами» и не только
    Мультисписок имеет несколько указателей «next» и позволяет собирать несколько (до N) не связанных друг с другом списков по разным признакам, не дублируя полезной нагрузки.
    typedef enum {
      LI_A, LI_B, LI_N
    } ListIndex;
    
    enum { LEN = 40 };
    
    typedef struct {
      Entry* next[LI_N];
      char name[LEN];
    } Entry;
    
    typedef Entry* PEntry;
    PEntry heads[LI_N], tails[LI_N];    // считаем, что = NULL
    
    int isIn(ListIndex aIndex, Entry* aEntry)
    {
      return (aEntry->next[aIndex] || tails[aIndex] == aEntry);
    }
    
    void addExisting(ListIndex aIndex, Entry* aEntry)
    {
       /* Уже есть? Поскольку такой код чреват ошибками, лучше всё же проверить */
      if (isIn(aIndex, aEntry))
        return;
      if (tails[aIndex]) {
        tails[aIndex]->next = aEntry;
        tails[aIndex] = aEntry;
      } else {
        heads[aIndex] = tails[aIndex] = aEntry;
      }
    }
    
    Entry* addNew(ListIndex aIndex, char* aName)
    {
      int i;
      Entry* newEntry = (Entry*)malloc(sizeof(Entry));
      strncpy(entry->name, aName, LEN);
      entry->name[LEN-1] = '\0';
      for (i = 0; i < LI_N, ++i)
        entry->next[i] = NULL;
      addExisting(aIndex, entry);
      return entry;
    }
    
    /* closure — т.н. замыкание, передача контекста, из которого была
       вызвана функция, вызывающая callback */
    typedef void(*ListCallback)(Entry* aEntry, void* aClosure);
    
    void traverseList(ListIndex aIndex, ListCallback aCallback, void* aClosure)
    {
      Entry* entry = heads[aIndex];
      while (entry) {
        aCallback(aEntry, aClosure);
        entry = entry->next[aIndex];
      }
    }

    О замыкании можно прочитать тут: Для чего нужны замыкания в C++ и как вы их используете?
    Ответ написан
    Комментировать
  • Как рекурсивно записать из файла в бинарное дерево?

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

    Таким образом, задача — хранить дерево в файле с возможностью считать его в том же виде, как и записано.

    В боевой программе я бы посоветовал использовать XML или его двоичный аналог.
    <node question="У него есть колёса?">
      <yes><answer value="наркоман" /></yes>
      <no><node question="Он живёт в лесу?">…</node></no>
    </node>

    От двоичного аналога требуется работать с блоками (микропотоками внутри большого файла) и с директориями.

    Если считать, что наша XML-библиотека работает методом дописывания тэгов в конец файла, будет такой код записи (считаем, что код объектно-ориентированный, с классами Развилка и Ответ, поддерживающими интерфейс Узел).
    функ записатьФайл(xml : XmlПисатель)
      xml.записатьЗаголовок
      xml.открытьТэг("tree")
      дерево.корень.записатьXml(xml)
      xml.закрытьТэг("tree")
    
    функ Развилка.записатьXml(xml : XmlПисатель)
      xml.открытьТег("node")
      xml.атрибут("question", вопрос)
      xml.открытьТэг("yes")
      да.записатьXml(xml)
      xml.закрытьТэг("yes")
      xml.открытьТэг("no")
      нет.записатьXml(xml)
      xml.закрытьТэг("no")
      xml.закрытьТэг("node")
    
    функ Ответ.записатьXml(xml : XmlПисатель)
      xml.открытьТег("answer")
      xml.атрибут("value", результат)
      xml.закрытьТэг("answer")

    Чтение будет такое (считаем, что читатель потоковый, автоматически пропускает всё внутри тэга, если не дать команду «войти в тэг», в закончившемся тэге ничего не выводит, пока не дать команду «выйти из тэга», и нет никаких препятствий к тому, чтобы собирать дерево в отрыве от корня).
    функ считатьУзел(xml : XmlЧитатель) : узел
      тэг := xml.считатьТэг
      в_зависимости_от (тэг)
        если "node":
          узел := новый Развилка
          узел.вопрос = …
          xml.войтиВТэг
    
          xml.затребоватьТэг("yes")
          xml.войтиВТэг
          узел.да = считатьУзел(xml)
          xml.выйтиИзТэга
    
          xml.затребоватьТэг("no")
          xml.войтиВТэг
          узел.нет = считатьУзел(xml)
          xml.выйтиИзТэга
    
          xml.выйтиИзТэга
          верни узел
        если "answer":
          узел := новый Ответ
          узел.результат = …
          верни узел
        иначе:
          ошибка
    
    функ Дерево.считатьXml(xml : XmlЧитатель)
      xml.затребоватьТэг("tree")
      xml.войтиВТэг
      узел := считатьУзел(xml)
      xml.выйтиИзТэга
      установитьКорень(узел)


    В учебной задаче — пометить каждый узел (вопрос или ответ), примерно так.
    Q
    У него есть колёса?
    A
    наркоман
    Q
    Он живёт в лесу?
    …

    Опять-таки, возможен двоичный аналог. Код точно такой же, только команды «войти/выйти» не нужны.

    Ах да. У вас в тэгах написано «Си». Скажите хоть, каким образом вы налаживаете указатели на два разных типа узлов (ответы и развилки), и мы придумаем, как это сделать. Например, если ответ — это та же развилка, только без обоих сыновей…
    функ записатьXml(xml, узел)
      если узел.да≠⌀ и узел.нет≠⌀
        ветвь_для развилки
      иначе если узел.да=⌀ и узел.нет=⌀
        ветвь_для_ответа
      иначе ошибка
    Ответ написан
  • Как работать с bmp?

    @Mercury13
    Программист на «си с крестами» и не только
    Открыть как картинку: Lister из Total Commander, любой продвинутый просмотрщик картинок (XnView, FastStone…), любой нормальный графический редактор (Paint.NET, GIMP, PhotoShop…)

    Увидеть внутреннее устройство: любой HEX-просмотрщик (проще всего Lister из Total Commander) или HEX-редактор. К сожалению, текстовым редактором двоичный файл лучше не открывать, файл может необратимо испортиться.

    Работать программно: насколько мне известно, поддерживает Qt.
    Если именно на Си — https://sourceforge.net/projects/libimage/

    Написать поддержку самому: достаточно качественное описание есть ан англовике: https://en.wikipedia.org/wiki/BMP_file_format
    Также вот несколько готовых структур: jenyay.net/Programming/Bmp paulbourke.net/dataformats/bmp
    Вот пример самодельной поддержки BMP для OpenGL: paulbourke.net/dataformats/bmp/BITMAP.C
    Ответ написан
    1 комментарий
  • Как исправить конфликт заголовочных файлов?

    @Mercury13
    Программист на «си с крестами» и не только
    Да тут у нас циклическая зависимость по интерфейсам! Такого не потерпит ни один язык, и Си тоже.
    Если в трёх модулях есть какие-то общие типы, их обычно выносят в отдельный хедер с названием вроде «MyUnitDefines.h».

    Как это выглядит технически? Пусть Unit3 описывает переменную extern Type1 var3;
    // Начало Unit1
    // Начало Unit2
    // Начало Unit3
    // Начало Unit1
       // Поскольку сработала защита от двойного включения, второго включения не было.
       // Так что кода typedef int Type1; к сожалению, йок.
    // Конец Unit1
    
    extern Type1 var3;    // Type1, естественно, не определён. 
                          // Си (как минимум старые редакции) это понимает как extern int Type1 var3.
    // Конец Unit3
    // Конец Unit2
    
    // Определение стоит ниже по коду, но это ПЕРВОЕ включение.
    typedef int Type1;
    // Конец Unit1

    (Да, препроцессор тупо копипастит код и не больше.)

    Циклическую зависимость по реализациям (то есть в C-файлах) большинство языков терпят. И Си тоже.
    Ответ написан
    Комментировать
  • Как синхронизируются потоки на низком уровне?

    @Mercury13
    Программист на «си с крестами» и не только
    Этого мало.
    Первое. Нужны особые операции, которые гарантированно выполняются атомарно. Например (из исходников Delphi)
    function InterlockedAdd(var Addend: Integer; Increment: Integer): Integer;
    asm
          MOV   ECX,EAX
          MOV   EAX,EDX
     LOCK XADD  [ECX],EAX
          ADD   EAX,EDX
    end;

    Здесь префикс LOCK (блокирование шины) и даёт атомарность. Также используют операцию XCHG (exchange) — единственная атомарная без префикса LOCK.

    На основе этого можно устроить объект, который называется spinlock. Крутим цикл, пока система не скажет: свободно. Из Википедии.
    mov eax, spinlock_address
    mov ebx, SPINLOCK_BUSY
    
    wait_cycle:
    xchg [eax], ebx  ; xchg - единственная инструкция, являющаяся атомарной без префикса lock
    cmp ebx, SPINLOCK_FREE
    jnz wait_cycle
    
    ; < критическая секция захвачена данным потоком, здесь идёт работа с разделяемым ресурсом >
    
    mov eax, spinlock_address
    mov ebx, SPINLOCK_FREE
    xchg [eax], ebx  ; используется xchg для атомарного изменения
    ; последние 3 инструкции лучше заменить на mov [spinlock_address], SPINLOCK_FREE -
    ; это увеличит скорость за счёт отсутствия лишней блокировки шины, а mov и так выполнится атомарно
    ; (но только если адрес spinlock_address выровнен по границе двойного слова)


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

    В однопроцессорном ядре никаких спинлоков нет. Если надо захватить ресурс, а он чей-то — сразу отдаём управление другому.

    UPD1. Почему не сразу отдавать работу другому процессу. Хороший тон — защищать мьютексом не системные вызовы (которые действительно долги) а какие-нибудь структуры данных вроде связных списков и атомарных struct. Так что вероятность, что объект просидит занятым долго, крайне мала. В настоящем мьютексе есть очередь с приоритетом, которая защищается, как ни странно, спинлоком. И этого достаточно.
    Ответ написан
    2 комментария
  • Где и как хранится имя переменной?

    @Mercury13
    Программист на «си с крестами» и не только
    Для компилируемого языка (коим является Си) — в объектном файле (и будет выброшено, когда линкер соберёт EXE) и в отладочных данных. Больше нигде. Переменные различаем их адресом.
    Возьми DosBox, возьми старый добрый Turbo Assembler и собери какой-нибудь COM-файл (пускай даже по руководству). А затем возьми хакерский просмотрщик типа Hiew и посмотри дизассемблер того, что получилось. Почему COM — потому что устроен он просто.
    Ответ написан
    Комментировать
  • Что изучить C или C++?

    @Mercury13
    Программист на «си с крестами» и не только
    Информационная безопасность требует от вас понимать, что такое эксплойт. Потому нужны оба языка. Может, не очень глубоко, но какой-то код надо писать на том и на другом.
    Дело в том, что «няшная сишка» стала отличным полигоном для эксплойтов, да и низкоуровневые библиотеки пишут часто на ней (для компактности). А на Си++ пишут современный софт.
    Раз вы на ПэХаПэ, вы не понимаете, что такое указатель, и потому лучше начать с Си++. Просто потому, что как-то можно программировать без указателей, а понять, что это за чёрт указатель — дело наживное.
    Ответ написан
  • Почему не компилируется код на С (gcc)?

    @Mercury13
    Программист на «си с крестами» и не только
    Я бы пенял на ложную тревогу антивируса.

    Кроме того, программа неверна. В ней card_name используется раньше, чем заполняется чем-то стóящим.
    Ответ написан
  • Приведение signed int к unsigned int?

    @Mercury13
    Программист на «си с крестами» и не только
    Видимо, связано с авариями переполнения. Представьте себе такую штуку: −128 + 255 = 127. Если в процессоре будут аппаратные аварии переполнения, это самое переполнение и произойдёт: −128 − 1 = ???. Беззнаковый тип аварий переполнения не вызывает.

    UPD. Так и есть. В Си знаковое переполнение — UB: такое переполнение может разделить с остатком, вызвать аварию или упереться в границы. В GCC есть ключи -ftrapv и -fwrapv — что делать при таком пререполнении.
    Беззнаковое — обязательно деление с остатком.

    UPD2. Стандарты Си и LIA несколько противоречат друг другу, и можно предположить, что знаковое переполнение зависит от реализации. Это не UB — можно сделать хороший код, полагающийся на такое поведение. Но если x86 так поступает, то не обязательно, что так поступают все.
    Ответ написан
  • Что означает char **s на языке Си?

    @Mercury13
    Программист на «си с крестами» и не только
    Указатель на указатель.

    Используется…
    1. Для многомерных массивов (в Си нечасто — ведь есть настоящие многомерные массивы; обычно в структурах посложнее, чем тупой массив M×N). Вот у меня есть самодельный (неполный) аналог std::deque (динамический массив, состоящий из кусочков и никогда ничего не перемещающий). Там внутри std::vector<T*>, который служит массивом массивов. А у вектора внутри — T**.
    2. Для массивов (1-е измерение) строк (2-е измерение).
    3. Для передачи указателя по указателю (см. описание функции strtod).
    4. (Крайне редко) И просто как указатель на указатель

    Как инициализировать?
    1.
    char row0[] = { 'A', 'B' };
    char row1[] = { 'C', 'D' };
    char* arr1[] = { row0, row1 };
    char** data1 = arr;
    
    2. 
    char* arr2[] = { "AB", "CD" };
    char** data2 = arr2;
    
    3. 
    char* errptr;
    int r = strtod("10", &errptr);
    return (*errptr != '\0') ? ERROR : r;
    
    4.
    char* data = "Data";
    char** pData = &data;
    Ответ написан
    2 комментария
  • Что написано в строке указатель или ссылка?

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

    а) Либо так называемый reinterpret_cast или type pun — берётся кусок памяти serv_addr неизвестного типа и рассматривается как совсем другой тип, sockaddr_in. Корректность такого «взять кусок памяти и рассмотреть как другой тип» остаётся за программистом, возможны тонкие ошибки кроссплатформенности.

    б) (в данном примере, скорее всего, нет, но тоже бывает) Либо так называемый const_cast — переменную, отмеченную как const, мы рассматриваем как неконстантную того же типа. Опять-таки, корректность такого преобразования остаётся за программистом: если переменная окажется в неизменяемой памяти, а мы её решим изменить, будет нехорошо.

    То и другое — либо сильно обобщённый код оперирует указателями «на что угодно», либо какая-то разглючка, либо плохая архитектура, либо последствия того, что где-то приходится опускаться на самый низкий уровень (обработка сообщений Windows, плагинная система).

    Подобное преобразование типа выполнено в стиле Си (взять указатель на переменную и преобразовать в другой тип-указатель).

    Кстати, в коде ещё одна ошибка кроссплатфроменности — константа -1 только для Linux. Портабельно — макрос SOCKET_ERROR.

    UPD. Это именно что reinterpret в сильно обобщённом (и сильно системном) коде. Функция connect принимает указатель на кусок памяти, который состоит из двухбайтового названия протокола и сетевого адреса произвольной длины (тип sockaddr). Тип sockaddr_in — это как будет эта выглядеть эта память для адресов IPv4 (протокол AF_INET). Одна и та же функция connect может подключаться по IPv4, IPv6, Apple Talk, Bluetooth и куче других протоколов, ныне известных разве что историкам. Другой протокол, другое AF_, другие sockaddr. Так что берём версию для IPv4 (sockaddr_in), заполняем её и передаём в connect, преобразовав в «более общий» тип sockaddr. Опознав, что имеем дело с IPv4, функция connect снова проинтерпретирует эту память как sockaddr_in.
    Ответ написан
    3 комментария
  • C++ как инкапсулировать глобальную функцию в обьект?

    @Mercury13
    Программист на «си с крестами» и не только
    Что вы имеете в виду? Инициализировать и закрыть библиотеку по мере появления-исчезновения объектов? Вот мой код (работает с cURL, но понятно).

    В чём его смысл? Как только объект cURL требуется первый раз, инициализируем глобальный объект. Теперь, скорее всего, cURL будет разрушен с его деструктором. Но если в каком-то потоке объект cURL будет слишком долго жить — ничего, подождём.

    (Внимание, тут гонка, если одновременно потребуются два объекта cURL. Нам такое не нужно, но если вдруг — защитите по принципу Singleton’а.)

    namespace curl {
    
        std::atomic<size_t> nLib(0);
    
        class _Lib
        {
        public:
            bool isIn = false;
            ~_Lib();
        };
    
        _Lib lib;
    
        void addLib()
        {
            int q = ++nLib;
            if (q == 1) {
                lib.isIn = true;
                q = ++nLib;
                curl_global_init(CURL_GLOBAL_ALL);
            }
            //std::cout << "Added lib, now " << q << std::endl;
        }
    
        void releaseLib()
        {
            int q = --nLib;
            if (q == 0) {
                //std::cout << "Cleaned up lib" << std::endl;
                curl_global_cleanup();
            } else {
                //std::cout << "Released lib, now " << q << std::endl;
            }
        }
    
        _Lib::~_Lib()
        {
            if (isIn)
                releaseLib();
        }
    
    }
    
    curl::Curl::Curl()
    {
        addLib();
        fData.handle = curl_easy_init();
    }
    
    curl::Curl::~Curl()
    {
        if (fData.handle)
            curl_easy_cleanup(fData.handle);
        releaseLib();
    }
    Ответ написан
    Комментировать
  • Как сделать преобразование hex to float правильно?

    @Mercury13
    Программист на «си с крестами» и не только
    Мы присвоили float’у целое число. Надо использовать union.
    union {
            float asF;
            uint32_t asI;
        };
        asF = 0x3EAE147B; // в десятичной - 0.340
        std::cout << asF << ' ' << std::hex << asI << std::endl;
        asI = 0x3EAE147B;
        std::cout << asF << ' ' << std::hex << asI << std::endl;
        return 0;
    Ответ написан
    Комментировать
  • Как исправить функцию с переменным числом аргументов?

    @Mercury13
    Программист на «си с крестами» и не только
    char *format = (char*)malloc((sizeof(PACKAGE_NAME) + sizeof(PACKAGE_VERSION) + sizeof(msg)) * sizeof(arguments));

    У вас выделяется (и к тому же не освобождается) буфер совершенно рандомного размера. У меня работает и так, но это вопрос везения. Даже если написатьchar format[200]; будет лучше.

    1. Более точно (запас допустим, нехватка  — нет) высчитать длину буфера.
    2. Освободить его, наконец!
    static const char* FORMAT1 = "[%s-%s]: %s\n";
      char *format = (char*)malloc(strlen(FORMAT1) + strlen(msg) + strlen(PACKAGE_NAME) + strlen(PACKAGE_VERSION) + 1);
      sprintf(format, "[%s-%s]: %s\n", PACKAGE_NAME, PACKAGE_VERSION, msg);
      va_start(arguments, msg);
      vprintf(format, arguments);
      va_end(arguments);
      free(format);


    Более удачное, но менее универсальное решение — printf заголовок, vprintf строку.
    printf("[%s-%s]: ", PACKAGE_NAME, PACKAGE_VERSION);
    
      va_list arguments;
      va_start(arguments, msg);
      vprintf(msg, arguments);
      va_end(arguments);
    
      putchar('\n');


    UPD1.
    Ваша новая ошибка — в коде
    char *msgbuf = (char*)malloc(strlen(msg) * sizeof(arguments));

    Раз уж хотите так, решение — вычислить длину буфера через snprintf.
    Ответ написан
  • Как побороть warnings?

    @Mercury13
    Программист на «си с крестами» и не только
    19/20. Полю int* argkeys::shortName вы присваиваете строковый литерал, то есть const char*.
    26/27. 4-му полю int option::val вы присваиваете argkeys::shortName то есть int*.
    25. Переменная не задействована.
    30. Функция ничего не возвращает.

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

    Что могу предположить? В структуре int* shortName заменил бы на int id или char id, и присвоил бы ему символьный литерал (то есть 'h'). Это автоматически решит и второе предупреждение (как только заменим там shortName на id).
    А 25 и 30 — сначала допишите программу до конца.
    Ответ написан
    Комментировать
  • Как изменить присвоенное значение переменной char?

    @Mercury13
    Программист на «си с крестами» и не только
    В Си нет даже строкового типа. Вся работа со строками идёт на самом низком уровне.

    Строка 9.
    1. Использовать if (strcmp(string_1, "0") == 0).
    2. Тело if сейчас пустое, убрать точку с запятой. А лучше — использовать фигурные скобки.

    Строка 10.
    1. Выход за границы диапазона.
    2. Если заменить 7 на 6, то в модели строк Си ничего не делает — если строка состоит из нуля, NUL и пяти символов мусора, то что ни пиши в мусор, значение не изменится.
    3. Если надо присвоить завершающий NUL — используй '\0'.
    4. Если хочешь просто присвоить строке значение «00», используй strcpy/strncpy.
    Ответ написан
    Комментировать
  • Как пропустить весь поток для того, чтобы можно было воспользоваться getchar()?

    @Mercury13
    Программист на «си с крестами» и не только
    fflush(stdin);

    Но такое поведение «новодел» и не заспецифицировано жёстко. В Windows работает, в Linux, говорят, тоже, а в BSD и его производных (Mac) — уже нет.

    Кто-то подсказывает такую штуку
    if ( ferror( stdin ) || feof( stdin ) )
        clearerr( stdin );
        else rewind( stdin );
    Ответ написан
    2 комментария
  • Как правильно распределить память?

    @Mercury13
    Программист на «си с крестами» и не только
    Что у нас такое cur_dir?
    У функции strcat_s три параметра…
    errno_t strcat_s(
       char *strDestination,
       size_t numberOfElements,
       const char *strSource 
    );

    В C++ есть также шаблонная перегрузка с двумя параметрами.
    template <size_t size>
    errno_t strcat_s(
       char (&strDestination)[size],
       const char *strSource 
    ); // C++ only

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

    P.S. А лучше, конечно, будет использовать контейнеры наподобие std::string.
    Ответ написан
    3 комментария
  • Почему не компилируется helloworld с использованием wxWidgets?

    @Mercury13
    Программист на «си с крестами» и не только
    Project → Build Options → Search Directories → Linker
    Ответ написан