Ошибка сегментации при изменении строки с помощью указателей?

Контекст

Я изучаю C и пытаюсь перевернуть строку с помощью указателей. (Я знаю, что вы можете использовать массив; это больше об указателях.)

Проблема

Я продолжаю получать ошибки сегментации при попытке запустить приведенный ниже код. GCC, похоже, не нравится строка *end = *begin;. Почему?

Тем более, что мой код почти идентичен незлой функции C, уже обсужденной в другом вопросе

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

void my_strrev(char* begin){
    char temp;
    char* end;
    end = begin + strlen(begin) - 1;

    while(end>begin){
        temp = *end;
        *end = *begin;
        *begin = temp;
        end--;
        begin++;
    }
}

main(){
    char *string = "foobar";
    my_strrev(string);
    printf("%s", string);
}

person brice    schedule 23.01.2010    source источник
comment
На самом деле это дубликат этого сообщения, пытающийся записать в постоянную строку - голосование за повторное открытие, чтобы его можно было повторно открыть. закрыт для правильного дублирования.   -  person Ken Y-N    schedule 21.09.2016
comment
@ KenY-N Для меня это имеет смысл :) Проголосовал за закрытие.   -  person brice    schedule 22.09.2016


Ответы (8)


Одна проблема связана с параметром, который вы передаете функции:

char *string = "foobar";

Это статическая строка, выделенная в части, доступной только для чтения. Когда вы пытаетесь перезаписать его

*end = *begin;

вы получите segfault.

Попробуйте с

char string[] = "foobar";

и вы должны заметить разницу.

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

person Remo.D    schedule 23.01.2010
comment
Что-то вроде char *string = strdup( "foobar" ); подойдет. - person x4u; 23.01.2010
comment
Большое спасибо Remo.D. Вот и все! - person brice; 23.01.2010
comment
@ x4u. Да, но вы должны освободить память, возвращаемую strdup (), когда закончите. Я признаю, что в данном случае это не важно, но все же я бы не предлагал использовать strdup (), если это не единственный разумный вариант. - person Remo.D; 23.01.2010
comment
@brice: yw :) Что вы имеете в виду под чтением из постоянной памяти? - person Remo.D; 23.01.2010
comment
@ Remo.D: Я понятия не имел, что char str [] и char * str будут разными. У меня создалось впечатление, что они создадут точно такой же объект в памяти, поэтому мне нужно будет включить свой google-foo и узнать больше! Здесь есть хорошее обсуждение: stackoverflow.com/questions/1704407/ - person brice; 23.01.2010
comment
Чтобы быть предельно ясным, char *s = "blah" выделяет место для хранения 5 символов, которое может быть или не быть доступным только для чтения. В большинстве случаев хранилище находится в сегменте памяти, доступном только для чтения, вместе с машинным кодом для программных инструкций. С другой стороны, char s[] = "blah"; выделяет в стеке изменяемый массив из 5 символов (т. Е. Локальное хранилище). Это всегда можно изменить. Взгляните на (stackoverflow. com / questions / 2036096 /) для получения дополнительных сведений о литеральных строках. - person D.Shawley; 23.01.2010
comment
понял: Керниган и Ричи, глава 5.5, стр. 104, 2-е издание, Язык программирования C. указатель может быть впоследствии изменен, чтобы указывать в другом месте, но результат не определен, если вы попытаетесь изменить содержимое строки. - person brice; 24.01.2010

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

#include <stdio.h>

void reverse(char *str){    
    int length=0,i=0;

    while(str[i++]!='\0')
        length++;

    for(i=0;i<length/2;i++){
        str[length]=str[i];
        str[i]=str[length-i-1];
        str[length-i-1]=str[length];
    }

    str[length]='\0';
}

int main(int argc, char *argv[]){

    reverse(argv[1]);

    return 0;
}
person user720694    schedule 14.08.2012

В вашем коде есть следующее:

*end--;
*begin++;

Это чистая удача, что это работает правильно (на самом деле, причина в приоритете оператора). Похоже, вы хотели, чтобы код действительно выполнял

(*end)--;
(*begin)++;

Что совершенно неверно. В вашем понимании операции происходят как

  • уменьшить end, а затем разыменовать его
  • увеличить begin, а затем разыменовать его

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

end--;
begin++;

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

person ezpz    schedule 23.01.2010
comment
Спасибо, что уловили это. Отредактировал. - person brice; 24.01.2010

Это было бы на месте и с помощью указателей

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

 void reve(char *s)
 {
    for(char *end = s + (strlen(s) - 1); end > s ; --end, ++s)
    {
        (*s) ^= (*end);
        (*end) ^= (*s);
        (*s) ^= (*end);
    }
 }

int main(void)
{
    char *c = malloc(sizeof(char *) * 250);
    scanf("%s", c);
    reve(c);
    printf("\nReverse String %s", c);
}
person nmd    schedule 13.01.2012

Измените char *string = "foobar"; на char string[] = "foobar";. Проблема в том, что char * указывает на постоянную память, которую вы затем пытаетесь изменить, вызывая ошибку сегментации.

person Kyle Lutz    schedule 23.01.2010

Это создает небольшую (ish) рекурсивную функцию и работает, сохраняя значения на пути вниз по стеку и увеличивая указатель на начало строки (* s) на обратном пути вверх (return).

Умный на вид код, но ужасный с точки зрения использования стека.

#include <stdio.h>

char *reverse_r(char val, char *s, char *n)
{
    if (*n)
        s = reverse_r(*n, s, n+1);
   *s = val;
   return s+1;
}

int main(int argc, char *argv[])
{
    char *aString;

    if (argc < 2)
    {
        printf("Usage: RSIP <string>\n");
        return 0;
    }

    aString = argv[1];
    printf("String to reverse: %s\n", aString );

    reverse_r(*aString, aString, aString+1); 
    printf("Reversed String:   %s\n", aString );

    return 0;
}
person Simon Peverett    schedule 22.02.2012

Вот моя версия переворота строки C.

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

int main (int argc, const char * argv[])
{
    char str[] = "foobar";
    printf("String:%s\n", str);
    int len = (int)strlen(str);
    printf("Lenth of str: %d\n" , len);
    int i = 0, j = len - 1;
    while(i < j){
        char temp = str[i];
        str[i] = str[j];
        str[j] = temp;
        i++;
        j--;
    }

    printf("Reverse of String:%s\n", str);
    return 0;
}
person dushshantha    schedule 10.12.2011

Ниже вы можете увидеть мой код для этой проблемы:

#include <string>
#include <iostream>

char* strRev(char* str)
{
    char *first,*last;

    if (!str || !*str)
        return str;

    size_t len = strlen(str);
    for (first = str, last = &str[len] - 1; first < last ; first++, last--)
    {
        str[len] = *first;
        *first = *last;
        *last = str[len];
    }
    str[len] = '\0';
    return str;
}

int main()
{
    char test[13] = "A new string";
    std::cout << strRev(test) << std::endl;
    return 0;
}
person Reza    schedule 10.03.2018