strcat добавляет мусор в строку

Я пытаюсь перевернуть предложение, не меняя порядок слов,

Например: "Hello World" => "olleH dlroW"

Вот мой код:

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

char * reverseWords(const char *text);
char * reverseWord(char *word);

int main () {
  char *text = "Hello World";
  char *result = reverseWords(text);
  char *expected_result = "olleH dlroW";
  printf("%s == %s\n", result, expected_result);
  printf("%d\n", strcmp(result, expected_result));
  return 0;
}

char *
reverseWords (const char *text) {
  // This function takes a string and reverses it words.
  int i, j;
  size_t len = strlen(text);
  size_t text_size = len * sizeof(char);
  // output containst the output or the result
  char *output;

  // temp_word is a temporary variable,
  // it contains each word and it will be
  // empty after each space.
  char *temp_word;

  // temp_char is a temporary variable,
  // it contains the current character
  // within the for loop below.
  char temp_char;

  // allocating memory for output.
  output = (char *) malloc (text_size + 1);

  for(i = 0; i < len; i++) {

    // if the text[i] is space, just append it
    if (text[i] == ' ') {
      output[i] = ' ';
    }

    // if the text[i] is NULL, just get out of the loop
    if (text[i] == '\0') {
      break;
    }

    // allocate memory for the temp_word
    temp_word = (char *) malloc (text_size + 1);

    // set j to 0, so we can iterate only on the word
    j = 0;

    // while text[i + j] is not space or NULL, continue the loop
    while((text[i + j] != ' ') && (text[i + j] != '\0')) {

      // assign and cast test[i+j] to temp_char as a character,
      // (it reads it as string by default)
      temp_char = (char) text[i+j];

      // concat temp_char to the temp_word
      strcat(temp_word, &temp_char); // <= PROBLEM

      // add one to j
      j++;
    }

    // after the loop, concat the reversed version
    // of the word to the output
    strcat(output, reverseWord(temp_word));

    // if text[i+j] is space, concat space to the output
    if (text[i+j] == ' ')
      strcat(output, " ");

    // free the memory allocated for the temp_word
    free(temp_word);

    // add j to i, so u can skip 
    // the character that already read.
    i += j;
  }

  return output;
}

char *
reverseWord (char *word) {
  int i, j;
  size_t len = strlen(word);
  char *output;

  output = (char *) malloc (len + 1);

  j = 0;
  for(i = (len - 1); i >= 0; i--) {
    output[j++] = word[i];
  }

  return output;
}

Проблема в строке, которую я пометил <= PROBLEM. На первом слове, которое в данном случае «Привет», все работает нормально.

Во втором слове, которое в данном случае «Мир», оно добавляет ненужные символы к temp_word, я проверил его с gdb, temp_char не содержит ненужных, но когда strcat запускается, последний символ, добавленный к temp_word, будет чем-то как W\006,

Он добавляет \006 ко всем символам во втором слове,

Вывод, который я вижу на терминале, в порядке, но печать strcmp и сравнение result с expected_result возвращает -94.

  • В чем может быть проблема?
  • Что за символ \006?
  • Почему strcat добавляет его?
  • Как я могу предотвратить такое поведение?

person DarkSuniuM    schedule 30.12.2019    source источник
comment
Пожалуйста, подумайте, почему вы выделяете len + 1 байт... Почему там +1? Вы используете лишний байт, который выделяете?   -  person Some programmer dude    schedule 30.12.2019
comment
Также, пожалуйста, подумайте о том, что происходит с памятью, которую вы выделяете в reverseWord. Когда и где он свободен?   -  person Some programmer dude    schedule 30.12.2019
comment
@Someprogrammerdude На самом деле я новичок в C, спасибо, что указали на то, о чем мне нужно больше узнать, первое, у меня нет идей, второе было одной из моих проблем, но, конечно, я не могу free переменная а потом вернуть   -  person DarkSuniuM    schedule 30.12.2019
comment
подумайте об использовании функции strtok и о том, как она может предотвратить утечку памяти. передайте вывод strtok вашей функции обратного слова, а затем выполните реверсирование на месте, заменяя элементы слова, пока не будет достигнут его конец. Как вы можете использовать вывод C-строки с нулевым завершением strtok, чтобы избежать разделения вашей оперативной памяти с помощью malloc?   -  person Joel Trauger    schedule 30.12.2019


Ответы (3)


strcat() ожидает адреса 1-го символа "C"-строк, которые на самом деле являются char-массивами, в которых хотя бы один элемент равен '\0'.

Ни память, на которую указывает temp_word, ни память, на которую указывает &temp_char, не удовлетворяют таким требованиям.

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

Возможным решением было бы изменить

      temp_word = (char *) malloc (text_size + 1);

стать

      temp_word = malloc (text_size + 1); /* Not the issue but the cast is 
                                             just useless in C. */
      temp_word[0] = '\0';

и это

        strcat(temp_word, &temp_char);

стать

        strcat(temp_word, (char[2]){temp_char});

Могут быть другие проблемы с остальной частью кода.

person alk    schedule 30.12.2019
comment
Это действительно сработало, но я не могу понять, как. Во-первых, temp_word[0] = '\0', почему мы помещаем указатель NULL в начало строки? - person DarkSuniuM; 30.12.2019
comment
Я имел в виду нулевой символ * - person DarkSuniuM; 30.12.2019
comment
@DarkSuniuM: не нулевой указатель, а нулевой символ. Цитируя мой ответ: C-строки... на самом деле представляют собой массивы символов, в которых хотя бы один элемент равен '\0'. - person alk; 30.12.2019

Основная причина нежелательных символов заключается в том, что вы используете неправильный ввод для 2-го аргумента функции strcat. см. объяснение ниже:

В начале вашей функции вы объявляете:

  int i, j;
  size_t len = strlen(text);
  size_t text_size = len * sizeof(char);
  // output containst the output or the result
  char *output;

  // temp_word is a temporary variable,
  // it contains each word and it will be
  // empty after each space.
  char *temp_word;

  // temp_char is a temporary variable,
  // it contains the current character
  // within the for loop below.
  char temp_char;

вы можете напечатать адреса переменных в стеке, они будут примерно такими:

printf("&temp_char=%p,&temp_word=%p,&output=%p,&text_size=%p\n", &temp_char, &temp_word,&output,&text_size);
result:    
&temp_char=0x7ffeea172a9f,&temp_word=0x7ffeea172aa0,&output=0x7ffeea172aa8,&text_size=0x7ffeea172ab0

Как видите, &temp_char(0x7ffeea172a9f) находится внизу стека, следующий 1 байт - &temp_word(0x7ffeea172aa0), следующие 8 байтов - &output(0x7ffeea172aa8) и так далее (я использовал 64-битную ОС, поэтому для указатель)

 // concat temp_char to the temp_word
  strcat(temp_word, &temp_char); // <= PROBLEM

описание strcat см. здесь: http://www.cplusplus.com/reference/cstring/strcat/

второй аргумент strcat = &temp_char = 0x7ffeea172a9f. strcat считает, что &temp_char(0x7ffeea172a9f) является отправной точкой исходной строки, вместо добавления только одного символа, как вы ожидаете, он добавит к temp_word все символы, начиная с &temp_char(0x7ffeea172a9f) , пока не встретит завершающий нулевой символ

person Ikarus    schedule 30.12.2019

Функция strcat работает со строками.

В этом фрагменте кода

  // assign and cast test[i+j] to temp_char as a character,
  // (it reads it as string by default)
  temp_char = (char) text[i+j];

  // concat temp_char to the temp_word
  strcat(temp_word, &temp_char); // <= PROBLEM

ни указатель temp_word, ни указатель &temp_char не указывают на строку.

Кроме того, массив output не дополняется завершающим нулем, например, когда исходная строка состоит из пробелов.

В любом случае ваш подход слишком сложен и имеет много избыточного кода, например, условие в цикле for и условие в выражении if, которые дублируют друг друга.

  for(i = 0; i < len; i++) {

    //…

    // if the text[i] is NULL, just get out of the loop
    if (text[i] == '\0') {
      break;
    }

Функцию можно написать проще, как это показано в демонстрационной программе ниже.

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

char * reverse_words( const char *s )
{
    char *result = malloc( strlen( s ) + 1 );

    if ( result != NULL )
    {
        char *p = result;

        while ( *s != '\0' )
        {
            while ( isblank( ( unsigned char )*s ) )
            {
                *p++ = *s++;
            }


            const char *q = s;

            while ( !isblank( ( unsigned char )*q ) && *q != '\0' ) ++q;

            for ( const char *tmp = q; tmp != s; )
            {
                *p++ = *--tmp;
            }

            s = q;
        }

        *p = '\0';
    }

    return result;
}

int main(void) 
{
    const char *s = "Hello World";

    char *result = reverse_words( s );

    puts( s );
    puts( result );

    free( result );

    return 0;
}

Вывод программы

Hello World
olleH dlroW
person Vlad from Moscow    schedule 30.12.2019