Ошибка в очень похожих функциях при работе со строками в C

#c #c-strings

#c #c-строки

Вопрос:

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

 char *strdelc3(char *s, char ch){
for(int i=0,j=0; i!=strlen(s) 1;   i)
 if(s[i]!=ch){
  s[j]=s[i];
    j;
 }
return s;
}
  

И когда я передаю строку и символ в качестве аргументов:

 main(){
char s[20]="mary";
puts(strdelc3(s,'r'));
}
The output is: Segmentation fault(core dumped),
  

что, согласно моим исследованиям, означает, что я обращаюсь к памяти, которая мне не принадлежит.
Решения имели этот код:

 char *strdelc4(char *s, char ch){ /*Correct*/
int i,j;
for(i=0, j=0; s[i]!='';   i)
 if(s[i]!=ch){
  s[j]=s[i];
    j;
 }
s[j]='';
return s;
}
  

Которая в основном равна моей, однако эта часть работает нормально!
Поскольку два кода настолько похожи, я не вижу ничего плохого в моем…
Я уже изучил оба, но я не вижу, в чем проблема с моим… Может кто-нибудь помочь?

Комментарии:

1. Почему i!=strlen(s) 1 в первом примере? Я думаю, что в этом и заключается проблема. Зачем добавлять один?

2. Между функциями есть два различия. Попробуйте их одну за другой, чтобы понять, почему каждая из них создает разные проблемы.

3. Проблема в том, что когда i есть strlen(s) , вы переместите нулевой байт, и длина строки уменьшится, поэтому i никогда не будет равна strlen(s) 1 . Всегда плохая идея помещать strlen в условный цикл (это огромная трата времени). Но в этом случае это особенно плохо.

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

5. Ааааа, вау, спасибо, пользователь3386109, теперь я понимаю! когда i = 5, strlen (s) 1 = 4, и я буду продолжать увеличиваться, не будучи равным моему условию.

Ответ №1:

Проблема в вашем условном цикле:

 i!=strlen(s) 1
  

Вы пытаетесь использовать strlen(s) 1 здесь, чтобы избежать необходимости добавлять нулевой байт. Но при этом strlen(s) изменяется, как только вы перемещаете завершающий нулевой байт.

На первых 4 итерациях цикла strlen(s) равно 4. На следующей итерации i равно 4, а strlen(s) 1 равно 5, поэтому вы снова входите в цикл. Затем вы перемещаете нулевой байт. Теперь на следующей итерации strlen(s) равно 3, а i равно 5. Условие по-прежнему верно, поэтому вы продолжаете, отходя от конца строки. Это вызывает неопределенное поведение, которое в этом случае вызывает сбой.

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

Ответ №2:

Еще более простая версия кода использовала бы do - while цикл вместо for() :

 char *strdelc5idx(char *s, char ch){
    int i=0, j=0;

    do {
        if (s[i] != ch)
            s[j  ] = s[i];
    } while (s[i  ] != 0);

    return s;
}
  

Это скопирует завершающий строку символ NUL перед его тестированием, поэтому вам не нужно иметь для этого отдельную инструкцию. Однако для этого требуется отложить i приращение, чтобы условие цикла в конце итерации проверяло тот же символ, который был скопирован в итерации. В результате i и j больше не отображаются вместе, что может сделать этот код менее разборчивым на первый взгляд.

Эквивалентная версия указателя:

 char *strdelc5ptr(char *s, char ch){
    char *d = s, *f = s;

    do {
        if (*f != ch)
            *d   = *f;
    } while (*f  );

    return s;
}