Ошибка программирования C - PullRequest
       13

Ошибка программирования C

1 голос
/ 17 сентября 2010
int main(void) {
    char *input;
    printf("prompt>");
    scanf("%s", input);
    printf("%s", input);
    return 0;
}

запрос> ввод

RUN FAILED (выходное значение 138, общее время: 3 с)

Что не так с кодом? Должен быть либо scanf (), либо вторым printf (). Ввод неизвестной длины. Многие люди говорят, что нужно просто создать массив символов длиной 'X' для хранения ввода. Просто хотел узнать, почему этот код работает.

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

int main(void) {
    /* prompt */
    char input;
    printf("prompt>");
    scanf("%s", &input);
    printf("%s", &input);
    return 0;
}

Ответы [ 6 ]

6 голосов
/ 17 сентября 2010

Ваша конкретная проблема в том, что у вас нет хранилища за input. Это неинициализированный указатель, указывающий на случайное место в памяти, которое вряд ли пригодится.

Вы можете использовать что-то вроде:

char *input = malloc (100);
// check that input != NULL
// use it
free (input);

или

char input[100];

но у вас есть серьезные проблемы с использованием scanf (см. Ниже).


Вы не должны никогда использовать неограниченный %s в scanf (или любой из его вариантов, если только вы не полностью контролируете вход). Это опасная практика, склонная к переполнению буфера, и чем быстрее вы избавитесь от привычки, тем лучше. Это похоже на gets() таким образом.

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

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

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

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

#define OK       0
#define NO_INPUT 1
#define TOO_LONG 2
static int getLine (char *prmpt, char *buff, size_t sz) {
    int ch, extra;

    // Get line with buffer overrun protection.
    if (prmpt != NULL) {
        printf ("%s", prmpt);
        fflush (stdout);
    }
    if (fgets (buff, sz, stdin) == NULL)
        return NO_INPUT;

    // If it was too long, there'll be no newline. In that case, we flush
    // to end of line so that excess doesn't affect the next call.
    if (buff[strlen(buff)-1] != '\n') {
        extra = 0;
        while (((ch = getchar()) != '\n') && (ch != EOF))
            extra = 1;
        return (extra == 1) ? TOO_LONG : OK;
    }

    // Otherwise remove newline and give string back to caller.
    buff[strlen(buff)-1] = '\0';
    return OK;
}

int main(void) {
    char input[10];
    int rc = getLine ("prompt> ", input, sizeof (input));
    switch (rc) {
        case NO_INPUT: printf ("\nNo input recieved\n"); break;
        case TOO_LONG: printf ("Too long, truncated input below:\n");
        default: printf("Your input was [%s]\n", input);
    }
    return 0;
}

Дайте это выстрел, это гораздо надежнее, чем использовать scanf("%s") самостоятельно.


Что касается вашего обновления, спрашивающего, почему это работает:

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

int main(void) {
    /* prompt */
    char input;
    printf("prompt>");
    scanf("%s", &input);
    printf("%s", &input);
    return 0;
}

Это неопределенный код. Период. Вы выделяете место только для символа, но сканируете в строке. Поскольку строка представляет собой массив символов из всех символов , за которым следует нулевой символ , единственной строкой, которую можно безопасно ввести, будет пустая.

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

Это ничем не отличается от выделения char input[100] с последующим вводом 200 символов, это все еще переполнение буфера и его следует избегать.

Обсуждение ниже основано на конкретной реализации C, не обязательно всех реализациях.

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

Кроме того, вы можете обнаружить, что вы также можете безопасно перезаписать восемь байтов целочисленного значения argc и указателя argv (они, вероятно, все еще там, даже если вы их не используете, нет смысла иметь два разных набора запуска) код только для сохранения нескольких байтов в стеке).

Если вы напишете дальше, вы в конечном итоге перезапишите адрес возврата из main в свой код запуска. Тогда вы узнаете об этом, так как ваш код отправится в la-la land, когда main выйдет.

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

Это не делает неопределенное поведение менее плохой вещью.

3 голосов
/ 17 сентября 2010
  char *input;

Это только указатель - нет выделенного пространства данных для хранения данных, которые собирает scanf.

Попробуйте вместо этого

char input[100];
1 голос
/ 17 сентября 2010

Попробуйте: -

int main(void) { 
char input[100]; 
printf("prompt>"); 
scanf("%99s", input); 
printf("%s", input); 
return 0; 

}

Это ограничит строку до 99 байтов.Обратите внимание: "% s" == строка символов, разделенных пробелом или новой строкой, т.е.вы получаете только первое слово!

Я думаю, что вы действительно хотите:

#include <stdio.h>
int main(void) { 
    char input[99]; 
    printf("prompt>"); 
    fgets(input,99,stdin);
    printf("->%s<-", input); 
    return 0; 
} 

Возможно, вам нужно добавить код, чтобы избавиться от нежелательных символов новой строки!

1 голос
/ 17 сентября 2010

Вы забыли выделить память перед использованием указателя.

Попробуйте:

int main(void) {
    char input[256];
    printf("prompt>");
    scanf("%s", input);
    printf("%s", input);
    return 0;
}

или даже:

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

int main(void) {
    char *input = (char *) malloc(sizeof(char) * 256));
    printf("prompt>");
    scanf("%s", input);
    printf("%s", input);
    return 0;
}
1 голос
/ 17 сентября 2010

Какой компилятор вы используете? В Turbo C 3.0 это работает. Попробуйте этот вариант:

#include <stdio.h>
#include <alloc.h>
int main(void) 
{
    char *input = (char*)calloc(100, sizeof(char));
    printf("prompt>");
    scanf("%s", input);
    printf("%s", input);
    free(input);
    return 0;
}
1 голос
/ 17 сентября 2010

Возможно, вы захотите попробовать scanf("%c", input) внутри цикла while с вашим символом-разделителем. Вы также должны сделать ввод массивом char input[X], где X - это число, достаточное для хранения наиболее вероятных значений для вашего ввода. Я хотел бы сначала попытаться сделать массив ввода.

...