Ответы пользователя по тегу C
  • Взгляд опытных программмистов, что улучшить и исправить?

    bingo347
    @bingo347
    Crazy on performance...
    1. Много дублирующегося кода в функции check_field.
    Можно вынести в константу массив массивов с выигрышными позициями и проверять в цикле.
    При этом позиций проверяется 7, хотя их должно быть 8.

    2. Проверку на ничью в той же check_field, можно делать ранний выход из цикла если найдена первая пустая клетка.

    3. Вынести в enum результат функции check_field, сейчас там магические числа возвращаются.

    4. Ввод имен игроков в функции game_with_people.
    Буффер на 10 байт на стеке легко переполнить и будет UB.

    5. В функции main опять повторяющийся код на вывод меню.

    6. Нет проверки на некорректный ввод в scanf.

    7. В game_with_people проверки на результат check_field можно сделать через switch case.

    8. Если игрок введет некорректную клетку, то ход уйдёт другому игроку.

    9. Можно перетереть уже занятую клетку.
    Ответ написан
    3 комментария
  • Как выглядел Си до K&R?

    bingo347
    @bingo347
    Crazy on performance...
    Книга по языку не является стандартом языка.
    Первым стандартом для C является ANSI C (C89), до него язык был не стандартизирован и сильно зависел от конкретного компилятора (хотя кое-где, особенно в embedded, это и сейчас можно встретить).
    Ответ написан
    1 комментарий
  • Как в строке удалить ненужный символ и при этом сократить саму строку?

    bingo347
    @bingo347
    Crazy on performance...
    ss = (char*)calloc(n, sizeof(char*)); // выделяем память под строку
    Здесь Вы выделяете гораздо больше байт, чем Вам нужно, sizeof(char*) будет равен 8 на 64 битных архитектурах, когда sizeof(char) - 1. Но при этом стоит не забывать про место под '\0' в конце строки.

    gets(ss);
    printf("Введите свое слово (не забываем про длину строки): ");
    scanf("%s", ss);
    ss = (char*)realloc(i, sizeof(char*));
    gets(ss);
    Почитайте, что делает функция gets. Опять же проблема с размерами символа. Ну и у realloc несколько другая сигнатура. И вообще realloc достаточно недешевая операция, не за чем ее делать в цикле много раз над одним указателем, достаточно 1 раз в конце.

    if (ss[i] != NULL) // проверка на нулевой указатель
    Проверку на NULL нужно делать сразу после аллокации памяти. А вот в цикле ее делать незачем.

    ss[j] = ss[j++];// здесь я заменял тот символ на следующий, тем самым перекидывая его в конец
    Операция j++ меняет j.

    for (int i = 0; i < sizeof(ss); i++)// ну а тут пытался поменять размер
    Операция sizeof(ss) даст размер char*, то есть размер указателя, что соответствует 8 на 64 битных архитектурах. Для вычисления длины строки (терминированной символом '\0') есть функция strlen. А в данном случае вообще можно посчитать количество удаленных символов и из него вычислить длину результирующей строки, что будет дешевле.

    Ну и по самому алгоритму. Все можно сделать за 1 проход, подсчетом удаляемых символов и перемещением текущего символа на количество удаленных символов назад.
    Ответ написан
    Комментировать
  • Как сделать проверку одних символов перед и после другого символа на Си?

    bingo347
    @bingo347
    Crazy on performance...
    Я подумываю сделать проверку символов до и после запятой, но не знаю, как это сделать. Помоги мне, пожалуйста.
    Проверяйте на число символы in[indexIN - 1] и in[indexIN + 1], при этом стоит учесть выход за границу при indexIN == 0.

    Для начала можно вынести проверку на число в макрос, что бы не дублировать код:
    #define IS_DIGIT(I) (in[(I)] >= '0' && in[(I)] <= '9')

    Ну и само условие в if тогда будет выглядеть так:
    if (IS_DIGIT(indexIN) || (in[indexIN] == ',' && indexIN != 0 && IS_DIGIT(indexIN - 1) && IS_DIGIT(indexIN + 1)))
        indexIN++;
    Ответ написан
    Комментировать
  • Хайп вокруг ЯП Rust и C?

    bingo347
    @bingo347
    Crazy on performance...
    Насколько критичной проблемой для программиста является ручное управление памятью, которое называют недостатком языка Си?
    Неосвобожденная память (утечка памяти) - это самое безобидное, что может произойти.
    - Сделать free дважды - это UB
    - Забыли проверить результат malloc/calloc/realloc на не NULL, разыменовали - UB
    - Почитали память, в которую ни разу не писали - UB
    - Разыменовали указатель после free - UB
    - Гонка данных - UB
    - ...и еще дофига всего, от чего в лучшем случае программа просто будет работать неправильно, например спалит Ваш пароль, или переведет Ваши деньги на другой счет 10 раз.

    Новый язык программирования Раст, как заявляют, лишен этого недостатка

    Система типов Rust гарантирует лишь одно - в safe коде не будет UB. Утечка памяти, кстати, не является UB, поэтому память вполне себе можно утечь в safe коде, помимо циклических счетчиков ссылок std дает немало возможностей сделать это напрямую:
    https://doc.rust-lang.org/beta/std/mem/fn.forget.html
    https://doc.rust-lang.org/beta/std/mem/struct.Manu...
    https://doc.rust-lang.org/beta/std/boxed/struct.Bo...
    https://doc.rust-lang.org/beta/std/vec/struct.Vec....

    но разве число ошибок в программе зависит именно от наличия или отсутствия ручного управления памятью
    В Rust ручное управление памятью, как и в C и в C++, просто есть культура, что если некая структура аллоцировала память, то она ее освободит. Всякие Vec, Box и т.д. делают это в Drop. В C++ многие повседневные типы так же освобождают в деструкторе память, которую они выделили. Однако в Rust есть разделение на safe и unsafe код и для прикладного программирования safe возможностей более чем достаточно. В C++ же весь код unsafe.

    разве общее число ошибок не перераспределяется на другие недостатки программы
    Нет, не перераспределяется. Хорошая система типов действительно может избавить от многих ошибок, что в целом сделает ПО более надежным. Но надо понимать, что от всех ошибок не избавит ни что. Банальная дискоммуникация с заказчиком порождает огромное число багов.
    Но в Rust очень быстро привыкаешь к такому замечательному подходу в разработке, как Type Driven Development, который позволяет предупредить многие ошибки еще до написания кода. После Rust я стал применять этот подход и в других ЯП, и он работает очень хорошо, даже там, где типизация не такая строгая.

    являются ли ошибки с памятью ошибками программиста, а не компилятора и языка программирования
    Безусловно это ошибки программиста. Программисты, как правило, - это люди, а людям свойственно ошибаться. И хорошо, когда есть средства статического анализа, которые помогают предотвращать ошибки до выхода ПО в продакшн.

    P.S. Вот интересная статья про Rust к прочтению: https://steveklabnik.com/writing/a-sad-day-for-rust
    И к чему она привела: https://github.com/fafhrd91/actix-web-postmortem
    Ответ написан
    4 комментария
  • Неправильно сравниваются массивы в Си, почему?

    bingo347
    @bingo347
    Crazy on performance...
    Данное условие никогда не выполнитсяif (correct_Login == 1 && correct_Password == 1)

    Ну и почитайте на досуге: https://habr.com/ru/post/491534/
    Ответ написан
    1 комментарий
  • Как мне добавить этот код в header и использовать в другом коде?

    bingo347
    @bingo347
    Crazy on performance...
    Давайте по порядку.
    В представленном примере у нас реализация функции, которая не принимает параметров и возвращает указатель на структуру android_app

    struct android_app * - это возвращаемый функцией тип;
    GetAndroidApp - имя функции;
    Конструкция (void) после имени означает, что функция без аргументов, современные компиляторы позволяют писать просто пустые скобки ();
    В фигурных скобках пишут тело функции, то есть ее реализацию.

    Компиляторы C читают код сверху вниз, использовать можно только то, что объявлено выше. Кроме того некоторые сущности вообще могут компилироваться отдельно и уже после линковаться в 1 исполняемый файл.
    Для этого язык C поддерживает декларации сущностей без их реализации, при условии, что сущность будет реализована ниже или где-то еще, что прилинкуется на сборке.
    Для функций декларацией (так же говорят заголовком) является описание ее сигнатуры, то есть для GetAndroidApp таким заголовком будет:
    // описание структуры android_app должно быть выше заголовка его использующего,
    // иначе он будет невалидным. А вот описание полей (реализация) может быть и ниже
    struct android_app;
    
    struct android_app* GetAndroidApp();


    комментарии говорится что я могу добавить его в Header
    Это значит, что данные сигнатуры можно вынести в заголовочный файл (тот что с расширением .h), это позволит вставить его во множество исходников через директиву препроцессора #include
    Ответ написан
    1 комментарий
  • Когда использовать malloc() или calloc()?

    bingo347
    @bingo347
    Crazy on performance...
    Простая переменная выделяет память на стеке. Память на стеке должна быть фиксированного размера и известна в момент компиляции программы, иначе программа просто работать не будет.

    malloc() и calloc() выделяют память на куче, она вполне себе может быть любого размера (не больше чем в системе есть свободной памяти конечно) и не обязательно знать ее размер в момент компиляции. На стеке будет только указатель, у которого фиксированный размер.
    Ответ написан
    Комментировать
  • Как сделать оптимальную перестановку элементов массива с XORом индексов?

    bingo347
    @bingo347
    Crazy on performance...
    Так как все индексы мы xor'им с одним и тем же числом cryptor то повторы между i и i ^ cryptor будут заключены в равных отрезках, притом размер этого отрезка всегда будет ближайшей от cryptor степенью 2 сверху, а сами повторы будут распределены в разных половинах данных отрезков, длина половинок соответственно ближайшая от cryptor степенью 2 снизу.
    Ну и нужно не забывать, что есть крайний случай, когда cryptor равен 0, при котором перестановок вообще не будет
    uint8_t main_buffer[512];
    uint8_t cryptor;
    
    cryptor = readCryptor();
    
    if(cryptor != 0)
    {
        // вычислим ближайшую к cryptor степень 2 снизу
        uint8_t pow2 = 0x80;
        while((pow2 & cryptor) == 0) pow2 >>= 1;
    
        while(readNext512Bytes(main_buffer))
        {
            // один большой цикл заменим на 2 маленьких
            // k будет считать отрезки
            // i будет считать позицию внутри отрезка
            for(uint16_t k = 0; k < 512; k += pow2 << 1) for(uint8_t i = 0; i < pow2; i++)
            {
                uint8_t tmp;
                tmp = main_buffer[i + k];
                main_buffer[i + k] = main_buffer[(i + k) ^ cryptor];
                main_buffer[(i + k) ^ cryptor] = tmp;
            }
        }
    }
    Ответ написан
    1 комментарий
  • Как решить эту задачу си?

    bingo347
    @bingo347
    Crazy on performance...
    Для начала нужно понимание, что памятью на стеке управляет компилятор, и для каждой функции эта память имеет фиксированный размер. А все потому, что в ассемблере, в который скомпилится наш C код нет переменных, вместо них компилятор просто проставит смещения от указателя на конец стека на старте функции. Именно по этому компилятору важно знать размеры всех сущностей на стеке.
    Изменить этот размер в рантайме нельзя, поэтому единственный способ решить эту задачу с массивом символов на стеке - выделить массив с достаточным запасом памяти, чтоб хватило и на исходную строку и на вставляемую подстроку.
    А вот память в куче динамическая. И все аллокации работают именно с ней. И главное мы ее можем выделять в рантайме по мере надобности.
    Учитывая, что массивы в C - это по сути сахар над указателями, то смело можете заявлять своему преподу, что указатель на несколько подряд идущих char в куче и массив char - это одно и то же.

    Вообще, по нормальному я бы объявил структуру вроде такой:
    typedef struct {
    	char* buffer;
    	size_t capacity;
    	size_t length;
    } string;
    и дальше бы работал с ней, заодно разбив код на небольшие функции. Но преподы программирования народ от разработки далекий, и кроме своих лабараторок в программировании не бум-бум в большинстве своем. А в комментах к вопросу было про то, что структуры нельзя...
    Поэтому решение в лоб и в стиле универских лаб:
    #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
    	size_t capacity = 1 << 4; // на старте буду выделять 16 байт
    	size_t length = 0; // строка пока пустая, поэтому 0
    	char* str = malloc(capacity);
    	if (str == NULL) { // с памятью в куче может и облом быть
    		return 1; // вернем код ошибки
    	}
    	printf("Write string and press ENTER to continue:\n");
    	while(1) { // читать символы будем до скончания веков ))
    		char c = getchar();
    		if(c == 13 || c == 10) break; // ну или пока юзер не нажмет ентер
    		length++; // увеличим счетчик символов
    		if(length > capacity) { // если новый символ не влазит в выделеную память
    			capacity <<= 1; // то удвоим ее
    			char* new_str = realloc(str, capacity); // и перевыделим
    			if (new_str == NULL) { // опять чекнем, вдруг облом
    				free(str); // ресурсы надо освобождать
    				return 1; // вернем код ошибки
    			}
    			str = new_str; // в str теперь невалидный указатель, можем его заменить
    		}
    		str[length - 1] = c; // запомним считанный символ
    	}
    	// Здесь решение с вставкой подстроки
    	// с учетом того, что у нас строка str
    	// с длиной length и выделеной памятью под нее capacity
    	free(str); // ресурсы надо освобождать
    	return 0; // вернем код успешного завершения
    }
    с самой вставкой подстроки надеюсь справитесь?
    Ответ написан
    6 комментариев
  • Как разобраться с выделением памяти в массиве?

    bingo347
    @bingo347
    Crazy on performance...
    Если правильно понял вопрос, то можно выделить память единым куском под всю матрицу:int* matr = (int*)malloc(n * m * sizeof(int));а к конкретной ячейке обращаться как matr[i * n + j]
    тогда и освободить будет просто - free(matr) и все.

    а для текущего примера освобождение памяти будет выглядеть так:
    void free_matr(int m, int** matr) {
      for(int i = 0; i < m; i++) {
        free(matr[i]);
      }
      free(matr);
    }
    Ответ написан
    1 комментарий
  • Имена массивов в си?

    bingo347
    @bingo347
    Crazy on performance...
    Массивы в C - это сахар над арифметикой и разыменовыванием указателей
    Согласитесь, проще и нагляднее писать arr[i] чем *(arr + i)

    Так же, для хранения данных на стеке необходимо знать точный размер этих данных во время компиляции. Объявляя тип массива для переменной на стеке, мы еще и показываем компилятору, сколько памяти нам надо, то есть, когда мы пишемint arr[10];компилятор понимает, что тут нужно выделить sizeof(int) * 10 памяти на стеке
    Ответ написан
    Комментировать
  • Сколько ячеек памяти будет занято при инициализации указателя адресом литерала?

    bingo347
    @bingo347
    Crazy on performance...
    Во-первых, 1 ячейка памяти всегда имеет 1 фиксированный размер - машинное слово (на 64 битной архитектуре это будет 8 байт).
    Во-вторых, компилятор си достаточно умный, что бы понимать, что сложные структуры данных вроде массивов и структур могут хранить все свои данные в 1 ячейке памяти, хотя могут и состоять из элементов меньше машинного слова. Но при этом данные относящиеся к разным структурам будут записаны в разные ячейки, даже если где-то окажется пустота.
    В-третьих, массивы в си всего лишь сахар над указателями и арифметикой над указателями.
    В-четвертых, не забываем, что сам указатель занимает машинное слово.

    Отсюда делаем вывод, что оба представленных выражения полностью идентичны и оба займут 2 ячейки памяти, так как char занимает 1 байт, а массив из 6 char (5 букв и \0 символ) вполне влазит в 1 машинное слово, а указатель всегда имеет размер машинного слова. Вот если бы в строке было 8 символов (и последний не \0 символ), то понадобилось бы уже 3 ячейки памяти.
    Ответ написан
    1 комментарий