Динамическое увеличение размера строки C

#c #string #memory-leaks #dynamic-memory-allocation

Вопрос:

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

 #include <stdio.h>
#include <stdlib.h>

typedef struct Foo {
  const char* str;
  int size;
} Foo;

int main(void)
{
  int i;
  Foo foo;
  foo.str = NULL;
  foo.size = 0;

  for (;;) {
    for (i = 8; i <= 190; i  ) {
      if (GetAsyncKeyState(i) == -32767) { // if key is pressed
        foo.str = (char*)realloc(foo.str, (foo.size   1) * sizeof(char)); // Access violation reading location xxx
        sprintf(foo.str, "%s%c", foo.str, (char)i);
        foo.size  ;
      }
    }
  }

  return 0;
}
 

Любая помощь была бы признательна, так как у меня больше нет никаких идей. 🙁
Может быть, мне также следует динамически выделять объект Foo?

Комментарии:

1. sprintf(foo.str, "%s%c", foo.str, (char)i); . Спросите себя — каково содержание, foo.str когда это называется? Если ответ не «допустимая строка C с нулевым окончанием» каждый раз, то поведение не определено. И вам не нужно далеко ходить — даже просто сделайте это упражнение для самого первого звонка.

2. Просто в качестве примечания: Как указано в документации GetAsyncKeyState , вы не должны использовать наименее значимый бит возвращаемого значения, так как он ненадежен и существует только для обратной совместимости с 16-разрядными окнами.

3. Просто в качестве примечания: использование GetAsyncKeyState в режиме ожидания с занятостью-не лучший способ дождаться ввода данных пользователем, так как это приведет к 100% — ной загрузке процессора на одном процессоре, что не позволит другим потокам и процессам использовать этот процессор. Это также увеличит энергопотребление. Если вы пишете графическое приложение Windows, вам следует создать правильный цикл сообщений. Если вы пишете консольное приложение Windows, вам следует использовать ReadConsoleInput его вместо этого.

4. В стороне: если realloc() не получится, вы утечете память. Всегда используйте указатель tmp для проверки результата realloc() и назначайте его обратно вашему основному указателю только в случае успеха (при сбое ваш основной указатель все равно будет там , и тогда вам решать, как справиться с ошибкой).


Ответ №1:

Во-первых, для того, чтобы все было хорошо, вам нужно определить

 typedef struct Foo {
    char* str;
    int size
} Foo;
 

В противном случае, Foo действительно раздражает правильное изменение — вы вызываете неопределенное поведение, изменяя foo->str его после вызова перераспределения любым способом.

Ошибка seg на самом деле вызвана sprintf(foo.str, "%s%c", foo.str, (char)i); , а не вызовом realloc . foo.str является, в общем случае, не завершенным нулем.

На самом деле, вы дублируете работу sprintf , вообще звоня. realloc уже скопированы все ранее введенные символы f.str , поэтому все, что вам нужно сделать, это добавить один символ с помощью

 f.str[size] = (char) i;
 

Отредактируйте, чтобы ответить на комментарий:

Если бы мы хотели добавить строки (или, скорее, два Foo) вместе, мы могли бы сделать это следующим образом:

 void appendFoos(Foo* const first, const Foo* const second) {
    first->str = realloc(first->str, (first->size   second->size) * (sizeof(char)));
    memcpy(first->str   first->size, second->str, second->size);
    first->size  = second->size;
}
 

appendFoos Функция изменяется first путем добавления second к ней.

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

Комментарии:

1. это все еще не строка C

2. @0___________ Что вы имеете в виду? Мы не пытаемся сделать это строкой C.

3. @MarkSaving: "We're not trying to make it a C string" — Это утверждение кажется неправильным. Цитата из вопроса: "I'm currently creating a program that captures user's keypresses and stores them in a string."

4. @0___________ Это действительно возможно, потому f.str что указывает на выделение size 1 байтов. В приведении может не быть необходимости, но оно определенно полезно для ясности.

5. @0___________ Как это вызывает неопределенное поведение?

Ответ №2:

  1. const char *str — вы объявляете указатель на const char . Вы не можете писать в объект, на который ссылается ссылка, так как он вызывает UB
  2. Вы используете sprintf только для добавления char . В этом нет никакого смысла.
  3. Вам не нужен указатель в структуре.

Вам нужно установить параметры компилятора для компиляции **как язык C» не C https://i.stack.imgur.com/9V1pt.png

Я бы сделал это немного по-другому:

 typedef struct Foo {
    size_t size;
    char str[1];
} Foo;

Foo *addCharToFoo(Foo *f, char ch);
{
    if(f)
    {
        f = realloc(f, sizeof(*f)   f -> size);
    }
    else
    {
        f = realloc(f, sizeof(*f)   1);
        if(f) f-> size = 0
    }
    if(f) //check if realloc did not fail
    {
        f -> str[f -> size  ] = ch;
        f -> str[f -> size] = 0;
    }
    return f;
}
 

и в main

 int main(void)
{
    int i;
    Foo *foo = NULL, *tmp;

    for (;;) 
    {
        for (i = 8; i <= 190; i  ) 
        {
            if (GetAsyncKeyState(i) == -32767) { // if key is pressed
            if((tmp = addCharToFoo(f, i))
            {
                foo = tmp;
            }
            else
            /* do something - realloc failed*/
            }
        }
    }

    return 0;
}
 

Комментарии:

1. Моя среда IDE выдает мне ошибку, когда я пытаюсь написать char str[]; (неполный тип не допускается) :/

2. @czarson Микросодт. Они все еще не реализовали изменения, произошедшие с 1980 года

3. к счастью, это не приведет к ошибке, если я объявлю массив последним элементом структуры

Ответ №3:

sprintf(foo.str, "%s%c", foo.str, (char)i); плохо сформулирован: первый аргумент не может быть const char * . Вы должны увидеть сообщение об ошибке компилятора.

После исправления этого (make str be char * ) поведение не определено, поскольку исходная память, считываемая %s получателем, перекрывается с назначением.

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