C: использование указателя в качестве строки: непредсказуемое поведение - PullRequest
2 голосов
/ 09 декабря 2011

Я пишу программу на C, чтобы найти самую длинную строку в пользовательском вводе и напечатать длину строки и саму строку.Он успешно подсчитывает символы, но непредсказуемо терпит неудачу при сохранении самой строки.Возможно, я неправильно понимаю управление памятью в С, и кто-то может исправить меня.

РЕДАКТИРОВАТЬ: следующий вопрос: Теперь я понимаю, что блоки, следующие за символом dummy, нераспределены и, таким образом, открывают диапазон длякомпьютер, чтобы что-то с ними делать, но тогда почему хранилище некоторых символов все еще работает?Во втором примере, который я упоминаю, программа хранит символы в «нераспределенных» блоках, даже если это «не должно».Почему?

Переменные:

  • getchar() сохраняется в c каждый раз, когда i getchar()
  • i - это длина (пока)текущей строки, я getchar(), начиная с
  • longest_i - это длина самой длинной линии, которая до сих пор
  • twostr указывает на начало первой из двух строк:первый для текущей строки, второй для самой длинной строки на данный момент.Когда обнаруживается, что строка самая длинная, она копируется во вторую строку.Если будущая строка еще длиннее, она переопределяет часть второй строки, но это нормально, потому что я больше не буду ее использовать - вторая строка теперь будет начинаться в месте, расположенном дальше справа.
  • dummy дает twostr место для указания на

Вот как я визуализирую память, используемую переменными программы:

 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|\n| 7|11|15|c |u |r |r |e |n |t |\0|e |s |t |\0|p |r |e |v |l |o |n |g |e |s |t |\0|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

истинные операторы:

&c == 11
&i == 12
&longest_i == 13
&twostr = 14
&dummy = 15

программа:

#include <stdio.h>

int main()
{
    char c = '\0';
    int i, longest_i;
    char *twostr;
    longest_i = i = 0;
    char dummy = '\0';
    twostr = &dummy;

    while ((c=getchar()) != EOF)
    {
        if (c != '\n')
        {
            *(twostr+i) = c;
            i++;
        }
        else
        {
            *(twostr+i) = '\0';
            if (i > longest_i)
            {
                longest_i = i;
                for (i=0; (c=*(twostr+i)) != '\0'; ++i)
                    *(twostr+longest_i+1+i) = c;
            }
            i = 0;
        }
    }

    printf("length is %d\n", longest_i);
    for (i=0; (c=*(twostr+longest_i+1+i)) != '\0'; ++i)
        putchar(c);

    return 0;
}

С *(twostr+longest_i+1)) до '\0' непредсказуемо.Примеры:

вход:

longer line
line

выход:

length is 11
@

вход:

this is a line
this is a longer line
shorter line

выход:

length is 21
this is a longer lineÔÿ"

Ответы [ 7 ]

4 голосов
/ 09 декабря 2011

Вы фактически не выделяете память для записи!

char dummy = '\0'; // creates a char variable and puts \0 into it
twostr = &dummy; // sets twostr to point to the address of dummy

После этого вы просто записываете в память, которая идет после символа, выделенного фиктивной фигурой, и записываете, кто знает что.

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

Например, buffer ниже будет указывать на 256 байтов (в большинстве систем) памяти, что позволяет использовать строку длиной до 255 символов (так как у вас есть нулевой терминатор (\ 0) для хранения в конце).

char * buffer = (char *)malloc(sizeof(char) * 256);

Редактировать: Это выделит память из кучи, которую вы позже должны освободить, вызвав free(buffer);, когда закончите с ней. Альтернативой является использование места в стеке в соответствии с решением Андерса К.

2 голосов
/ 09 декабря 2011

Да, вы правы, говоря, что вы неправильно понимаете модель управления памятью C.

Например, в строке

*(twostr+i) = c;

это будет правильно, за исключением того факта, что twostr содержит адрес символа и только *twostr указывает на вашу память.Добавление к нему всего, кроме 0, для получения другого адреса и разыменования, которое приводит к неопределенному поведению, поскольку размер памяти, принадлежащей dummy, равен 1 байту.выделить часть памяти для хранения строки. Проще всего показать, как это правильно сделать, поэтому вот код с внесенными исправлениями:

#include <stdio.h>

int main()
{
    char c;
    int i, longest_i;
    char twostr[1024]; // twostr points to a block of memory 1024 bytes long
    char longest[1024]; // so does longest, where we will store the longest string

    longest_i = i = 0;
    char dummy = '\0';

    while ((c=getchar()) != EOF && i < 1024) // we check that i < 1024 so we don't
                                             // go outside the bounds of our arrays
    {
        if (c != '\n')
        {
            *(twostr+i) = c;
            i++;
        }
        else
        {
            twostr[i] = 0;
            if (i > longest_i)
            {
                longest_i = i;
                for (i = 0; twostr[i] != 0; ++i) { // 0 is the same as '\0'
                    longest[i] = twostr[i];
                    twostr[i] = 0; // fill twostr with NULLs
                }
            }
            i = 0;
        }
    }

    printf("length is %d\n", longest_i);
    for (i=0; longest[i] != 0; ++i)
        putchar(longest[i]);

    return 0;
}

Кроме того, способ визуализации вашей программыпеременные неверны.Это действительно будет что-то вроде этого:

Стек:

+---------+
|    c    |   1 byte
+---------+
|         |
|         |
|         |
|    i    |   4 bytes
+---------+
|         |
|         |
|         |
|longest_i|   4 bytes
+---------+
|         |
|         |
|         |

~~~~~~~~~~~

|         |
|         |
|  twostr |   1024 bytes
+---------+
|         |
|         |
|         |

~~~~~~~~~~~

|         |
|         |
| longest |   1024 bytes
+---------+
2 голосов
/ 09 декабря 2011

Ты разбиваешь свой стек. У вас есть только 1 байт для манекена char. На самом деле это должно быть что-то вроде:

манекен [1024];

Также необходимо убедиться, что вы не пишете больше 1024 или 1023 байтов, чтобы разрешить нулевой терминатор.

2 голосов
/ 09 декабря 2011

Вы не выделяете память для хранения символов, прочитанных getchar.Ваш указатель twostr является символьным указателем, указывающим на символьную переменную, а не на массив, но вы рассматриваете его как указатель на массив символов:

char *twostr;
....
char dummy = '\0';
twostr = &dummy;
....
*(twostr+i) = c;  // when i here is > 0 you are accessing invalid memory.

Вам нужно что-то вроде:

char *twostr = malloc(MAX);
// use it.
free(twostr);

Где MAX определяется на единицу больше максимальной длины строки в пользовательском вводе.

1 голос
/ 09 декабря 2011

Попробуйте следующий код. Надеюсь, вы получите ожидаемый результат:

#include <stdio.h>

#define LENGTH 1024

int main()
{
    char c;
    int i, longest_i;
    char twostr[LENGTH]=""; // twostr points to a block of memory 1024 bytes long
    char longest[LENGTH]=""; // so does longest, where we will store the longest string
longest_i = i = 0;
char dummy = '\0';

while ((c=getchar()) != EOF && i < LENGTH) // we check that i < 1024 so we don't
                                         // go outside the bounds of our arrays
{
    if (c != '\n')
    {
        *(twostr+i) = c;
        i++;
    }
    else
    {
        twostr[i] = 0;
        if (i > longest_i)
        {
            longest_i = i;
            for (i = 0; twostr[i] != 0; ++i) { // 0 is the same as '\0'
                longest[i] = twostr[i];
                twostr[i] = 0; // fill twostr with NULLs
            }
        }
        i = 0;
    }
}

printf("length is: %d\n", longest_i);
printf("And the word is: ");
puts(longest);
printf("\n");
return 0;
}
1 голос
/ 09 декабря 2011

Во-первых, вам нужно убедиться, что twostr имеет достаточно места для хранения строки, которой вы управляете. Скорее всего, вам потребуется добавить дополнительную логику для выделения начального пространства, а также для выделения дополнительного пространства при необходимости. Что-то вроде:

size_t twostrLen = 256;
char* twostr = malloc(twostrLen);

Затем, вставив в него данные, вам нужно убедиться, что вы выделите дополнительную память, если ваш индекс превысит текущую длину twostrLen:

if (i >= twostrLen) {
   char* tmp = twostr;
   twostrLen *= 2;
   twostr = malloc(twostrLen);
   memcpy(twostr, tmp, i-1);
   free(tmp);
}

Где i - это смещение от twostr, в которое вы собираетесь писать.

Наконец, при копировании из текущей строки в самую длинную строку, ваше условие завершения цикла равно c=*(twostr+i)) != '\0'. Это сработает, когда c соответствует '\0', выходя из цикла до того, как будет записан завершающий ноль. Вам нужно убедиться, что ноль записан, чтобы ваш цикл для печати строки работал правильно. Добавление следующего после внутреннего цикла for должно решить проблему:

*(twostr+longest_i+1+i) = 0;

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

Опять же, не забудьте проверить это longest_i+1+i < twostrLen перед записью в это место.

1 голос
/ 09 декабря 2011

twostr указывает на символ, однако вы воспринимаете его как буфер.

что вам нужно сделать, это создать буфер вместо которого может содержать больше символов

, например

static char dummy[512];
twostr = dummy;
...