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

    @res2001
    Developer, ex-admin
    В Си, да и в С++, стандартные целочисленные типы, поддерживаемые компилятором - это char, short, int, long long и их беззнаковые братья. Стандарт действительно не фиксирует размеры стандартных целочисленных типов. Это сделано потому что стандарт описывает абстрактный язык, который должен компилироваться для разных платформ.

    Компилятор же компилирует программу под конкретную платформу с конкретными соглашениями по типам. Ему заведомо известны размеры стандартных типов на данной конкретной платформе. Стандартная библиотека так же обычно пишется под конкретный компилятор и конкретную платформу.
    Так что (u)intX_t - это всегда define над стандартными типами. И ничего странного в этом нет.
    Ответ написан
    Комментировать
  • Почему в Си после main() не ставят ;?

    @res2001
    Developer, ex-admin
    В Си различают определение функции и предварительное объявление функции (он же прототип).
    Точка с запятой ставится только в случае предварительного объявления функции.
    Например:
    int func(int a, inb);     // предварительное объявление
    int func(int a, inb) { return a+b; }       // определение функции

    В вашем примере main - это определение функции - точка с запятой не ставится.
    Ответ написан
    Комментировать
  • Как настроить CMake в Clion для stm32 для подключения библиотек?

    @res2001
    Developer, ex-admin
    Попробуйте в include_directories() указывать полные пути. Стандартная переменная cmake ${CMAKE_CURRENT_SOURCE_DIR} содержит путь где лежит текущий файл cmakelists.txt, ее можно использовать для генерации полного пути.
    Обычно, если в проекте используется иерархия каталогов, то в каждом каталоге с исходниками лежит свой собственный cmakelists, а подключается он к нижележащему через add_subdirectory(). Собирайте в каждом каталоге свою цель (статическую библиотеку), потом эти цели указывайте в зависимостях для основной цели.
    Ответ написан
    Комментировать
  • В чем разница записи массива через указатели?

    @res2001
    Developer, ex-admin
    Приоритет операции * выше, чем у -
    Ответ написан
    Комментировать
  • Что и почему лучше подключить в C++? math.h или cmath? stdio.h или cstdio?

    @res2001
    Developer, ex-admin
    В С++ используют вариант где не указывают расширение .h в include. С расширением это для исходников на Си.
    Впрочем, как вы верно заметили, в плюсах это то же работает. Обычно внутри <cxxxx> просто делается #include <xxxx.h>
    Ответ написан
    Комментировать
  • Как подключить файл с помощью макроса к другому файлу?

    @res2001
    Developer, ex-admin
    У вас в вопросе 2 варианта main.
    В первом будет ошибка, во втором нет при сборке командой:
    gcc main.c function1.c

    Чтоб в первом случае не было ошибки нужно собирать командой:
    gcc main.c
    т.к. function1.c включается в main.c с помощью include и его не нужно повторно давать компилятору.
    Эта команда для сборки второго варианта main не подойдет.
    Ответ написан
  • Не могу понять почему не работает USER тред?

    @res2001
    Developer, ex-admin
    Видимо потому что main заканчивается и программа завершается вместе со всеми потоками.

    Вообще не понятно, что ты этим хотел сказать. Где вызов swapcontext? Без него стеки псевдопотоков не используются и переключение контекстов не происходит. Зачем они тогда тут нужны?
    В прошлом варианте с таймером, по крайней мере, была идея и было понятно, чего ты хотел добиться. Хоть оно и не работало.
    Ответ написан
  • Как решить проблему с Segmentation fault?

    @res2001
    Developer, ex-admin
    Ошибка выполнения stat для файла: No such file or directory

    Потому что:
    strcpy(filename, "mntEntry->mnt_dir");
    Вы записываете в filename строку "mntEntry->mnt_dir", а не каталог.
    Добавить затем слэш в конец можно так: strcat(filename, "/");
    Или вручную:
    size_t len = strlen(filename);
    filename[len] = '/';
    filename[len + 1] = '\0';

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

    Есть еще момент. В линукс есть константа PATH_MAX она задает максимальный размер пути к файлу. Рекомендую ваши буфера для хранения путей делать размером PATH_MAX.
    Ответ написан
  • Почему после ассемблера учить Си легче?

    @res2001
    Developer, ex-admin
    Утверждение пожалуй верно.

    Лично на мой взгляд это из-за того, что и в ассемблере и в Си есть указатели. Причем в ассемблере ничего кроме указателей для работы с памятью нет (имею ввиду именно оперативную память, а не регистровую или ПЗУ) и вам волей-неволей на ассемблере придется понять, что это такое и как реально работает память в компе. И с этими знаниями очень легко уже понять указатели в Си, т.к. это суть одно и то же, только синтаксис разный.

    В Си же есть альтернатива - можно попытаться использовать только автоматические или глобальные переменные и не использовать указатели. Но более-менее большой проект с такими самоограничениями не возможен, на мой взгляд. Из-за наличия альтернативы использования памяти, тема указателей (и ручного управления памятью) в Си кажется сложной и многих отпугивает.
    Но в реальности, понять Си гораздо проще, чем ассемблер. Так что не уверен, стоит ли подобная овчинка выделки.

    Если вам для чего-то нужен ассемблер, то видимо, стоит с ним разобраться. Но разбираться с ассемблером для изучения Си - это какой-то садомазохизм. Си довольно простой ЯП. Ассемблер сам по себе то же не сложен, но что бы на нем что-то писать нужно хорошо знать и понимать архитектуру конкретного процессора.

    У издательства ДМК пресс есть книжки по ассемблеру (в т.ч. по АРМ) и по архитектуре компьютеров, можете посмотреть у них на сайте, там же можно и купить. Что плохо, они не делают скидку на электронные книги, как многие другие.
    Изучать нужно ассемблер под ту платформу, на которой вы сможете писать тестовые программы. Обычно это х86, но при наличии, например, малинки или апельсинки можно и АРМ попробовать.
    Конечно можно и эмулятором воспользоваться, но это лишние проблемы. А может и не лишние - если уж вы хотите ассемблер, то это может пригодиться.
    Ответ написан
    Комментировать
  • Нужно ли писать суффиксы литералов?

    @res2001
    Developer, ex-admin
    Однако с точки зрения оптимизации, эстетики и/или стандартов, как писать все-таки лучше

    Как хотите.

    На самом деле опциями компилятора можно задать выдавать предупреждения (или даже ошибку) на подобное не явное приведение типов. Тогда это будет "не красиво" или даже вообще ошибка компиляции, и тут вам придется исправлять ситуацию добавив суффикс.
    Но подобные опции используют не часто. На сколько помню для gcc это опции -Wsign-conversion и/или -Wconversion но могу ошибаться. И, по моему, они даже не включаются через -Wall -Wextra -pedantic - обычно используют этот набор опций, когда хотят заставить компилятор выполнять больше проверок кода.
    Немного модифицировав опции можно добиться не предупреждения, а ошибки.

    Если вы пишите исполняемый файл, то вы контролируете опции сборки в makefile (или чем вы там пользуетесь) и вас этот вопрос может не парить вообще.
    Если вы пишете библиотеку, которую распространяете в исходниках, то есть вероятность, что пользователь при сборке захочет добавить своих опций и тогда ошибки/предупреждения могут проявится. Можете расставить суффиксы для обхода потенциальных проблем, а можете забить и оставить на усмотрение конечного пользователя библиотеки.
    Ответ написан
    Комментировать
  • Почему программа "Конвентер Валют" выдаёт 0.000000 при любом значении?

    @res2001
    Developer, ex-admin
    Ответ в комментариях
    Ответ написан
    Комментировать
  • Как присвоить динамическому массиву типа void* значение в Си?

    @res2001
    Developer, ex-admin
    Нельзя выделить память для произвольного типа, т.к. размер произвольного типа - произвольный. Память всегда выделяется конкретного размера.

    В вашем примере вы выделяете память для двух указателей (void*). На всех распространенных платформах указатель, не важно на какой тип он ссылается, имеет один и тот же размер.
    Нельзя сделать разъименование void*, т.к. это указатель с неопределенным типом - компилятор не знает какого типа данные лежат по адресу в указателе, а следовательно не может с ними корректно работать. Для нормальной работы нужно привести указатель к какому-нибудь типу и потом уже можно делать разъименование (ваш 3 пример).

    Ваш пример не корректен для х64 платформы, т.к. sizeof(void*) там 8 байт, а sizeof(int) - 4 байта.
    Вы mallocом выделяете 16 байт памяти (2 указателя), а в 3 присваивании (которое работает) присваиваете значение 10 старшей половине первого указателя. В общем выглядит как бред.
    Для х32 - корректен, т.к. тут sizeof(void*) == sizeof(int)

    Для копирования двух массивов произвольного типа и размера, нужно знать размер массива в байтах (не в элементах). Можно не знать тип, но знать размер необходимо - иначе ничего не поучится. Выделяете память заданного размера, приводите указатель к char* и побайтово копируете (memcpy).
    Приводить к int* и копировать intы в этом случае нельзя, т.к. массив может быть, например 3 байта или 33, тогда при копировании через приведение к int* вы неминуемо выйдете за границу массива.
    Ответ написан
    Комментировать
  • Как написать функцию sin из библиотеки math.h в Си?

    @res2001
    Developer, ex-admin
    У вас sum2 не инициализируется в начале, а в цикле вы туда уже чего-то прибавляете.
    Как думаете какой результат будет возвращен функцией? Зависит от того что лежало на стеке где теперь лежит sum2, а лежать там может все что угодно.
    Ответ написан
    Комментировать
  • Почему fopen устанавливает ошибку Invalid argument в errno при fopen в режиме r+ после w и fopen в режиме w+ после r?

    @res2001
    Developer, ex-admin
    Интересный вопрос. Ответа у меня нет, что-то не приходилось использовать режимы "+".
    Попробуйте обнулять errno перед вызовом fopen. Возможно значение, осталось от предыдущего вызова. Вообще читать errno надо, только если fopen вернул NULL. При нормальном завершении errno не меняется.
    И если pFile == NULL, очевидно, что не надо вызывать fclose.
    Ответ написан
  • Как передать статический двумерный массив типа float в функцию типа void в качестве аргумента на языке C?

    @res2001
    Developer, ex-admin
    1. В двумерных массивах в Си данные располагаются в памяти по строчно, т.е. сначала идет первая строка массива, затем вторая и т.д. У вас же индекс по строкам указан вторым. Вас тут спасает только то, что матрица квадратная и количество строк равно количеству столбцов.
    2. Внутри функции переданный массив - это не массив, это указатель float *nameMatrix. Соответственно вы не можете делать двойную индексацию nameMatrix[x][y]. Вам надо вычислять индекс вручную:
    *(nameMatrix + y * columnsMatrix + x) = a * (x + 1) * powf(sinf(y + b), 2);

    Для справки, индексация массива это синтаксический сахар для следующей операции: *(nameMatrix + i)
    Ответ написан
    Комментировать
  • Кроссплатформенное программирование на C?

    @res2001
    Developer, ex-admin
    Тут не важно GUI или нет (может быть любой интерфейс - сеть, файлы, потоки, процессы, и т.п. системные интерфейсы). Подход один и тот же. Если нужна кроссплатформенность, то ищите кроссплатформенные библиотеки, реализующие нужный интерфейс. Если таких нет (видимо плохо искал), то пишите свою. Это довольно сложно.
    Ответ написан
    Комментировать
  • Декларация и инициализация, в чем различия?

    @res2001
    Developer, ex-admin
    По моему, на русском чаще используют термин объявление, а не декларация. По крайней мере мне так привычнее.
    Объявление в одном из возможных переводов на английский звучит как declaration.
    https://en.cppreference.com/w/c/language/declarations
    https://en.cppreference.com/w/c/language/type

    Вы не правильно сопоставляете инициализацию и декларацию (объявление). Сопоставлять надо объявление и определение.
    Инициализация - всего лишь присвоение переменной некоторого начального значения. Она может быть при определении переменной или потом - не важно. Важно то, что перед инициализацией память для переменной должна быть выделена.
    Память выделяется когда переменная определяется.
    При объявлении память не выделяется, а только описывается (объявляется) тип. Тип включает в себя информацию о размере, выравнивании, возможных операциях, что-еще. Встроенные типы (int и т.п.) уже объявлены заранее и известны компилятору.
    Может быть предварительное объявление. Предварительных объявлений может быть сколько угодно много, если они не противоречат друг другу. Настоящее объявление может быть только одно.

    Понять различие между объявлением и определением на простом встроенном типе (int) довольно сложно, потому что сам тип уже известен, его не нужно объявлять. Для примера буду использовать структуру.

    Кроме того важно где конкретно в коде программы определена переменная - глобально (по отношению к файлу исходного кода) или локально (в функции).
    struct s;                     // предварительное объявление
    struct s { int v; };       // объявление структуры
    strcut s s1;                // определение глобальной структуры
    struct s s2 = { 0 };     // определение глобальной инициализированной структуры
    int main()
    {
      struct s s3;             // определение локальной структуры
    }

    Предварительное объявление - говорит о том, что где-то есть полное объявление. Тип объявленный только предварительным объявлением - не завершенный (не полный). Нельзя определить переменную по неполному типа. НО можно определить указатель на неполный тип. Но обращаться по указателю на неполный тип нельзя. Но присвоить адрес можно :-) Это можно использовать в своих интересах.
    После полного объявления типа уже можно определять переменные этого типа.
    s1 - глобальная не инициализированная структура. Память под нее выделяется в секции bss исполняемого файла. В исполняемом файле обычно не выделяется память непосредственно, но сохраняется размер секции. Загрузчик ОС читает размер секции из файла и выделяет память нужного размера. Глобальные не инициализированные переменные на самом деле инициализируются неявно нулем.
    s2 - глобальная инициализированная структура. Память под нее выделяется в секции data исполняемого файла. Тут уже не достаточно сохранить информацию о секции, т.к. есть начальные значения. Поэтому такие переменные непосредственно присутствуют в исполняемом файле (точнее выделена память под них и присвоено начальное значение). Есть еще секция rodata - для константных данных.
    s3 - локальная не инициализированная переменная. Память под нее выделяется на стеке. Не зависимо от того инициализирована переменная или нет, память выделена, а значит в этой памяти уже что-то лежит - не бывает "пустой" памяти. В случае не инициализированной переменной на стеке, в памяти лежит мусор, который остался тут от прошлых действий.
    Ответ написан
    1 комментарий
  • Как создать указатель на вводимый текст?

    @res2001
    Developer, ex-admin
    Ну и покажите что вы пытались сделать.

    Вообще текст куда-то должен попасть после ввода. Для этого функции чтения вы должны дать буфер, куда она и запишет, все что прочитала.

    В типичной ситуации, когда не известно сколько данных будет введено, вы выделяете буфер некоторого размера (1Кб например), вызываете функцию чтения с этим буфером. Если функция полностью забила весь буфер и еще хочет - выделяете буфер большего размера, копируете содержимое предыдущего буфера, предыдущий буфер удаляете. Снова вызываете функцию чтения со смещением в новом буфере. И так пока не прочитаются все данные.
    Если же введенные данные должны быть как-то обработаны, то обычно все что прочиталось сразу обрабатывается, после обработки буфер обычно пустой (или заполнен остатками не обработанных данных, которые надо дополнить) и читаете снова в тот же буфер.

    Когда вы выделите буфер (с помощью malloc например), вы получите указатель. Пока этот указатель указывает на "пустой" буфер. После чтения в буфер, указатель будет указывать на прочтенные данные. Функция чтения вернет вам еще и длину прочитанных данных. Этого достаточно для дальнейшей работы с буфером.
    Ответ написан
    8 комментариев
  • Возможно ли создание своего типа данных - массива бит?

    @res2001
    Developer, ex-admin
    В п.1 написана какая-то синтаксически не корректная хрень, смысла которой я понять не могу.

    Массив бит или битовое поле можно реализовать, конечно. Фактически это будет массив байт (или любого другого беззнакового целого), можно его спрятать в typedef и реализовать над ним несколько операций отдельными функциями.
    Например в С++ есть специализация std::vector<bool>, которая для экономии памяти использует внутри битовый массив, а не массив bool, как можно было бы подумать.

    Объявляете байт и работаете с отдельными битами с помощью битовых операций. Если есть необходимость в более широком битовом поле, то можно все унифицировать: при инициализации задаете размер битового поля, вычисляете по заданному размеру размер массива (можно использовать uint8_t, uint16_t, uint32_t или uint64_t - любой беззнаковый целочисленный тип оптимальной длинны), выделяете память для массива.
    Операции над битовым полем:
    1. создание/удаление
    2. установить/снять бит по номеру
    3. проверить установлен ли бит по номеру
    4. опционально вычисление количества установленных бит и/или проверка пустое ли битовое поле.

    Все это не трудно сделать. Можно даже заморочится и сделать на макросах универсальный вариант, где тип данных для массива будет выбираться в зависимости от размера битового поля. Не уверен, что это надо на самом деле, вполне достаточно использовать массив uint8_t для всех размеров битового поля.

    Объявить переменную размером в один бит (или размером не кратным 8 бит) нельзя, потому что в современных компьютерах минимально адресуемая единица памяти - 1 байт. Была бы 1 бит - можно было бы объявлять переменные размером в 1(2,3, 11, ...) бит. Это архитектурное ограничение компьютеров, а не языка программирования.
    Ответ написан
    2 комментария
  • Как корректно отслеживать завершение потоков?

    @res2001
    Developer, ex-admin
    Предлагаемый POSIX вариант это join. Если не устраивает, то вы сами можете конструировать проверку завершения потоков теми средствами какие вам удобно и какие доступны.

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

    Имейте ввиду, что join все равно надо вызвать, когда удостоверитесь, что поток завершен. Чтоб окончательно освободить все ресурсы потока. Или надо заранее делать поток отсоединенным (detach). Я бы использовал join, а не detach.
    Ответ написан