strcat() и fgets() в C не работают

По той или иной причине я не могу заставить работать два экземпляра fgets() и strcat().

Это мой код:

#include <stdio.h>
#include <string.h>

int main()
{
  char str[150];

  char userName[8]; char password[8];
  printf("Enter username: ");fgets(userName, 9, stdin);
  printf("Enter password: ");scanf("%s", password);

  strcpy(str, "INSERT INTO USERS (ID,USERNAME,PASSWORD,IP) "  \
         "VALUES (1, '");
  strcat(str, userName);
  strcat(str, "', '");
  strcat(str, password);
  strcat(str, "', '120.132.12.1');");

  puts(str);
  return 0;
}

При получении строки для «пароля» я не могу использовать fgets() или выдает ошибку «Прервать ловушку: 6». В частности, я использую:

fgets(password, 9, stdin);

9 элементов, потому что я читал, что вам нужно учитывать нулевой завершающий символ в вашей сумме.

Я не понимаю этого, потому что, поскольку в массиве char столько же элементов, сколько и в userName, я думал, что код для получения строки должен быть практически таким же, кроме имени массива char.

Кроме того, strcat(), похоже, не работает при попытке добавить «str» с именем пользователя. Мой вывод выглядит примерно так:

Enter username: beckah12
Enter password: beckah12
INSERT INTO USERS (ID,USERNAME,PASSWORD,IP) VALUES (1, '', 'beckah12', '120.132.12.1')

Почему он пропускает "strcpy(str, userName)"?

Обновлять

Когда я объявляю эти две строки, происходит некоторое перекрытие памяти. Когда я объявил случайное целое число между двумя строками, он сохранил входные данные для обеих. Как исправить это перекрытие памяти с помощью более постоянного решения?


person beckah    schedule 12.05.2014    source источник
comment
Ваш второй аргумент fgets() слишком велик. Для char[8] должно быть 8. Кроме того, вы должны ограничить (и проверить) scanf: scanf("%7s", password);   -  person pmg    schedule 12.05.2014
comment
Ну, во-первых, вы сообщаете fgets, что в вашем буфере 9 символов, хотя в нем всего 8. Максимальное количество считываемых символов включая завершающий нуль.   -  person Gene    schedule 12.05.2014
comment
@pmg - я изменил свой код, и теперь он не будет ждать ввода пароля.   -  person beckah    schedule 12.05.2014
comment
Что, вероятно, происходит, так это то, что нулевой терминатор пароля хранится в первом байте имени пользователя, что создает впечатление, что имя пользователя представляет собой пустую строку (на самом деле это является пустой строкой). строку, если этот диагноз верен). Распечатайте значение username до и после чтения пароля. Или попробуйте пароль из 7 символов.   -  person Jonathan Leffler    schedule 12.05.2014
comment
Проблема не будет ждать означает, что fgets() закончилось место для строки данных до того, как она прочитала новую строку; поэтому второй fgets() читает новую строку и прекращает обработку. Считайте данные в более крупные строки (32, 64, 80, 256, 4096 байт), а затем проверьте длину и т. д. Помните, что fgets() включает новую строку во входную строку, когда она подходит. Когда он не подходит, он остается для следующей операции чтения. Это немного странно — что-то вроде ретро 80-х — ограничивать имя пользователя всего 8 символами, не так ли?   -  person Jonathan Leffler    schedule 12.05.2014
comment
@JonathanLeffler - именно это и происходит. Понятия не имею почему. Это не сработало при сокращении пароля до 7 символов, но сработало, когда я объявил случайное целое число между двумя строками. .. как исправить это перекрытие памяти с помощью более постоянного решения?   -  person beckah    schedule 12.05.2014
comment
При вводе beckah12 вам нужен буфер размером 10 или более, поскольку fgets() будет читать "beckah12\n", что равно 9 char и завершающему '\0'.   -  person chux - Reinstate Monica    schedule 12.05.2014
comment
Вы отредактировали свой вопрос, чтобы исправить (часть) проблему, о которой вы спрашивали. Пожалуйста, не делайте этого; это делает недействительными ответы, которые уже были опубликованы, и делает ваш вопрос бесполезным для будущих читателей. Я откатил правку.   -  person Keith Thompson    schedule 12.05.2014
comment
@KeithThompson- Понял. Извиняюсь. Я просто хотел больше сосредоточиться на перекрытии воспоминаний. Понял.   -  person beckah    schedule 12.05.2014


Ответы (3)


Для вашего fgets() вы правильно указали:

9 элементов, потому что я читал, что вам нужно учитывать нулевой завершающий символ в вашей сумме.

Но в ваших объявлениях переменных вы забыли об этом:

char userName[8]; char password[8];

Сделайте их char ....[9], и ваша программа больше не должна прерываться.

Но, как упомянул @chux:

если вы читаете целые строки текста, вам также придется иметь дело с символом новой строки, что делает ваш код примерно таким:

char userName[10]; char password[10];
...
fgets( userName, sizeof userName, stdin );
if( userName[strlen(userName)-1] == '\n' ) {
    //truncate new line character
    userName[strlen(userName)-1] = '\0';
}

... и все равно у вас возникнет проблема, если кто-то введет имя или пароль длиной более 8 символов. Итак, я бы предложил:

#define L_USER 8

char userName[256]; char password[256];
...
fgets( userName, sizeof userName, stdin );
if( strlen(userName) > L_USER ) {
    //truncate name if too long
    userName[L_USER] = '\0';
}    
if( userName[strlen(userName)-1] == '\n' ) {
    //truncate new line character
    userName[strlen(userName)-1] = '\0';
}
person Ingo Leonhardt    schedule 12.05.2014
comment
Нужно 10, чтобы справиться с входными данными для fgets(), такими как beckah12\n. - person chux - Reinstate Monica; 12.05.2014

Это радикальная переработка, но она также эффективна. Это больше похоже на то, что я бы использовал:

#include <assert.h>
#include <stdio.h>
#include <string.h>

static int prompt_for_data(const char *prompt, const char *what, char *buffer, size_t buflen)
{
    char line[256];
    assert(prompt != 0 && *prompt != '\0');
    assert(what != 0 && *what != '\0');
    assert(buffer != 0);
    assert(buflen > 1 && buflen < sizeof(line));

    printf("%s", prompt);
    if (fgets(line, sizeof(line), stdin) == 0)
    {
        fprintf(stderr, "EOF\n");
        return EOF;
    }
    size_t len = strlen(line);
    if (len == 0)
    {
        fprintf(stderr, "Zero length string? You can't use Control-@ in a %s\n",
                what);
        return EOF;
    }
    if (line[len - 1] != '\n')
    {
        fprintf(stderr, "Line far too long (%zu bytes)\n", len);
        return EOF;
    }
    line[--len] = '\0';
    /* Optionally chop leading and trailing white space - omitted */
    if (len > buflen - 1)
    {
        fprintf(stderr, "Maximum length of %s is %zu (you entered %zu characters)\n",
                what, buflen - 1, len);
        return EOF;
    }
    if (len == 0)
    {
        fprintf(stderr, "You're supposed to enter a value for the %s\n", what);
        return EOF;
    }
    strcpy(buffer, line);
    return 0;
}

int main(void)
{
    char userName[9];
    char password[9];

    if (prompt_for_data("Enter username: ", "username", userName, sizeof(userName)) == 0 &&
        prompt_for_data("Enter password: ", "password", password, sizeof(password)) == 0)
    {
        char str[150];
        snprintf(str, sizeof(str),
                 "INSERT INTO USERS (ID, USERNAME, PASSWORD, IP) VALUES"
                 "(%d, '%s', '%s', '%s');",
                 1, userName, password, "120.132.12.1");
        puts(str);
    }
    return 0;
}

Последовательность «запрашивать значение, читать строку, проверять ее длину, копировать в нужную переменную» заключена в функции prompt_for_data(). Затем он используется дважды (но я, вероятно, написал бы его как функцию, даже если бы он вызывался только один раз), чтобы получить имя пользователя и пароль. Затем, вместо использования strcat(), я использую snprintf() для создания выходной строки.

Вы также должны отметить, что если вы хотите изменить размер вашего имени пользователя и пароля с причудливых архаичных 8 символов плюс завершающий нуль на 32, единственные строки кода, которые изменяются, — это объявления userName и password. Это потому, что я осторожно использовал sizeof(userName) или sizeof(password), когда мне нужно было описать размер. Аналогично с другими переменными; например, строка fgets() не изменится, если я изменю размер переменной line. Обычно хорошей практикой является минимизация количества необходимых изменений при изменении размера переменной.

Обратите внимание, что у вас все еще есть проблемы с внедрением SQL. Если я наберу "O'Reilly" в качестве имени пользователя, SQL будет недействительным. Помните Little Bobby Tables! Одна хорошая вещь о коротких именах пользователей; это усложняет внедрение SQL.

Примеры запусков

$ ./up
Enter username: beckah12
Enter password: beckah12
INSERT INTO USERS (ID, USERNAME, PASSWORD, IP) VALUES(1, 'beckah12', 'beckah12', '120.132.12.1');
$ ./up
Enter username: absolom12
Maximum length of username is 8 (you entered 9 characters)
$ ./up
Enter username: beckah12
Enter password: absolom12
Maximum length of password is 8 (you entered 9 characters)
$ ./up
Enter username: 
You're supposed to enter a value for the username
$ ./up
Enter username: beckah12
Enter password: 
You're supposed to enter a value for the password
$ ./up
Enter username: EOF
$ ./up
Enter username: beckah12
Enter password: EOF
$ ./up
Enter username: nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
Line far too long (255 bytes)
$
person Jonathan Leffler    schedule 12.05.2014
comment
+1 за полный ответ. Минор: Вместо size_t len = strlen(line); ... line[len - 1], у которого есть педантичный потенциал для len == 0, что вы думаете о strtok(line, "\n"), чтобы обнулить типичный '\n'? - person chux - Reinstate Monica; 12.05.2014
comment
@chux: Хороший вопрос... но: я не assert(buflen > 1 && buflen < sizeof(line)), но это разумные предположения. В этом контексте единственный раз, когда вы можете получить len == 0, это когда не осталось данных для чтения, но это сообщается как EOF, поэтому я думаю, что код защищен от потери значимости. - person Jonathan Leffler; 12.05.2014
comment
Спасибо за внимание. Примечание. fgets() читает строку (массив char обычно заканчивается '\n'), а не строку C (массив char заканчивается '\0'). Таким образом, fgets() может прочитать редкую строку, начинающуюся с '\0', таким образом возвращая line, а не EOF, а strlen(line) == 0. Это странное дело, которое, как я думал, может вас заинтересовать. - person chux - Reinstate Monica; 12.05.2014
comment
Гррр; спасибо, @chux, за правильный вариант использования, о котором я забыл! - person Jonathan Leffler; 12.05.2014

Изменить линии

char userName[8]  to  char userName[9]

char password[8]  to  char password[9]

потому что это то, что вы указали в fgets(userName, 9, stdin); et. др..

person ryyker    schedule 12.05.2014