Программы выполняются правильно и затем segfaults - PullRequest
2 голосов
/ 22 декабря 2009

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

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

char *to_upper(char *src);

int main(void) {
    char *a = "hello world";
    printf("String at %p is \"%s\"\n", a, a);
    printf("Uppercase becomes \"%s\"\n", to_upper(a));
    printf("Uppercase becomes \"%s\"\n", to_upper(a));
    return 0;
}

char *to_upper(char *src) {
    char *dest;
    int i;
    for (i=0;i<strlen(src);i++) {
        if ( 71 < *(src + i) && 123 > *(src + i)){
            *(dest+i) = *(src + i) ^ 32;
        } else {
            *(dest+i) = *(src + i);
        }
    }
    return dest;
}

Это работает нормально и печатает именно то, что должно (включая повтор строки «HELLO WORLD»), но впоследствии заканчивается ошибкой сегментации. Что я не могу понять, так это то, что функция явно компилируется, выполняется и успешно возвращается, и поток в main продолжается. Так происходит ли ошибка сегментации в return 0?

Ответы [ 4 ]

18 голосов
/ 22 декабря 2009

dest не инициализирован в вашей функции to_upper(). Таким образом, вы перезаписываете какую-то случайную часть памяти, когда делаете это, и, очевидно, это вызывает сбой вашей программы при попытке возврата из main().

Если вы хотите изменить значение на месте, инициализируйте dest:

char *dest = src;

Если вы хотите сделать копию значения, попробуйте:

char *dest = strdup(src);

Если вы сделаете это, вам нужно убедиться, что кто-то вызывает free() по указателю, возвращенному to_upper() (если вас не волнует утечка памяти).

2 голосов
/ 22 декабря 2009

Как уже говорили другие, ваша проблема не в том, чтобы выделить достаточно места для dest. Есть еще одна, более тонкая проблема с вашим кодом.

Чтобы преобразовать в верхний регистр, вы проверяете заданное значение char, чтобы увидеть, находится ли оно между 71 и 123, и если это так, вы хорируете значение с 32. Это предполагает кодирование символов ASCII. ASCII - наиболее широко используемая кодировка, но не единственная.

Лучше написать код, который работает для каждого типа кодировки. Если бы мы были уверены, что 'a', 'b', ..., 'z' и 'A', 'B', ..., 'Z', смежны, то мы могли бы вычислить смещение из нижнего регистра буквы в верхнем регистре и используйте это, чтобы изменить регистр:

/* WARNING: WRONG CODE */
if (c >= 'a' && c <= 'z') c = c + 'A' - 'a';

Но, к сожалению, нет такой гарантии, предоставляемой стандартом C. Фактически кодировка EBCDIC является примером.

Итак, для преобразования в верхний регистр вы можете сделать это простым способом:

#include <ctype.h>
int d = toupper(c);

или сверните свои собственные:

/* Untested, modifies it in-place */
char *to_upper(char *src)
{
    static const char *lower = "abcdefghijklmnopqrstuvwxyz";
    static const char *upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    static size_t n = strlen(lower);
    size_t i;
    size_t m = strlen(src);

    for (i=0; i < m; ++i) {
        char *tmp;
        while ((tmp = strchr(lower, src[i])) != NULL) {
            src[i] = upper[tmp-lower];
        }
    }
}

Преимущество toupper() состоит в том, что он проверяет текущую локаль для преобразования символов в верхний регистр. Например, это может быть от æ до,, что обычно является правильным решением. Примечание : Я использую только символы английского и хинди, так что я могу ошибаться в своем конкретном примере!

2 голосов
/ 22 декабря 2009

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

  1. Динамически выделять буфер dest и возвращать значение указателя, которое вызывающий отвечает за освобождение;
  2. Назначьте dest для указания на src и измените значение на месте (в этом случае вам придется изменить объявление a в main () с char *a = "hello world"; на char a[] = "hello world";, в противном случае вы я буду пытаться изменить содержимое строкового литерала, который не определен);
  3. Передать целевой буфер в качестве отдельного аргумента.

Вариант 1 - динамически выделить целевой буфер:

char *to_upper(char *src)
{
  char *dest = malloc(strlen(src) + 1);
  ...
}

Вариант 2 - dest должен указывать на src и изменять строку на месте:

int main(void)
{
  char a[] = "hello world";
  ...
}
char *to_upper(char *src)
{
  char *dest = src;
  ...
}

Вариант 3 - main () передать целевой буфер в качестве аргумента:

int main(void)
{
  char *a = "hello world";
  char *b = malloc(strlen(a) + 1); // or char b[12];
  ...
  printf("Uppercase becomes %s\n", to_upper(a,b));
  ...
  free(b); // omit if b is statically allocated
  return 0;
}
char *to_upper(char *src, char *dest)
{
  ...
  return dest;
}

Из трех я предпочитаю третий вариант; вы не изменяете ввод (поэтому не имеет значения, является ли массив массивом или указателем на строковый литерал), и вы не распределяете обязанности по управлению памятью между функциями (т.е. main () несет полную ответственность за выделение и освобождение буфера назначения).

Я понимаю, что вы пытаетесь ознакомиться с принципами работы указателей и некоторыми другими низкоуровневыми деталями, но имейте в виду, что a[i] легче читать и следовать, чем *(a+i). Кроме того, в стандартной библиотеке есть ряд функций, таких как islower() и toupper(), которые не зависят от конкретных кодировок (таких как ASCII):

#include <ctype.h>
...
if (islower(src[i])
  dest[i] = toupper(src[i]);
1 голос
/ 22 декабря 2009

Как отмечают другие, ваша проблема в том, что char * dest не инициализирован. Вы можете изменить память src на месте, как предлагает Грег Хьюгилл, или использовать malloc для резервирования:

char *dest = (char *)malloc(strlen(src) + 1);

Обратите внимание, что использование strdup, предложенное Грегом, выполняет этот вызов malloc под прикрытием. «+ 1» - зарезервировать пространство для нулевого терминатора, «\ 0», который вы также должны копировать из src в dest. (Ваш текущий пример подходит только к strlen, который не включает нулевого терминатора.) Могу ли я предложить добавить такую ​​строку после цикла?

*(dest + i) = 0;

Это правильно завершит строку. Обратите внимание, что это применимо только в том случае, если вы решили пойти по пути malloc. Изменение памяти на месте или использование strdup решит эту проблему для вас. Я просто указываю на это, потому что вы упомянули, что пытались учиться.

Надеюсь, это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...