Другое

Символы мусора в строках C: Полное руководство по исправлению

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

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

c
#include <stdio.h>

int main() {
    char string1[100];
    char string2[100];

    int length1 = 0;
    int length2 = 0;

    printf("Enter the first string: ");
    scanf("%s", &string1);

    printf("Enter the second string: ");
    scanf("%s", &string2);

    for (int i = 0; i < 100; i++) {
        if (string1[i] == '\0') {
            // Empty block
        }
        else {
            length1++;
        }
    }

    for (int i = 0; i < 100; i++) {
        if (string2[i] == '\0') {
            // Empty block
        }
        else {
            length2++;
        }
    }

    for (int i = 0; i < length2; i++) {
        for (int j = 0; j < length1; j++) {
            if (string2[i] == string1[j]) {
                for (int k = j; k < length1 - 1; k++) {
                    if (k == length1 - 1) {
                        string1[k + 1] = '\0';
                    }
                    else {
                        string1[k] = string1[k + 1];
                    }
                }
            }
        }
    }

    printf("%s\n", string1);
    return 0;
}

Например, при вводе “asdf” и “f” вывод показывает “asd”, за которым следуют мусорные символы. Почему это происходит и как это исправить?

Ваш код на C отображает мусорные символы, потому что ваш алгоритм удаления строк не правильно поддерживает нуль-терминацию строки. Основная проблема заключается в том, что при сдвиге символов влево для удаления нежелательных символов вы не правильно обновляете нуль-терминатор, что вызывает printf("%s", string1) читать за пределами предполагаемого содержимого строки до тех пор, пока не встретит произвольный нулевой байт в памяти.


Содержание


Понимание проблемы

В C строки представляют собой нуль-терминированные массивы символов, что означает конец корректной строки отмечен '\0' (нуль-байтом). Когда вы используете printf("%s", string1), он выводит символы, начиная с начала, пока не встретит первый нулевой байт. Если ваша логика удаления не правильно поддерживает этот нуль-терминатор, printf будет продолжать чтение памяти за пределами вашей строки, выводя любые данные, которые там находятся, пока случайно не найдет нулевой байт.

Из статьи Википедии о нуль-терминированных строках мы понимаем, что “Дизайнер C Деннис Ритчи выбрал следовать конвенции нуль-терминирования, чтобы избежать ограничения на длину строки, и потому что, по его опыту, поддержание счетчика казалось менее удобным, чем использование терминатора.”

Ключевые проблемы в вашем коде

1. Небезопасный ввод с использованием scanf

c
scanf("%s", &string1);

Это опасно, потому что:

  • scanf("%s") не указывает максимальную длину, потенциально вызывая переполнение буфера
  • Если ввод заполнит ровно 99 символов, нуль-терминатор не будет добавлен
  • Оператор & не нужен (имена массивов при использовании в качестве параметров функции преобразуются в указатели)

Как отмечено в статье InformIT о безопасном программировании, “Некоторые важные свойства массивов и строк критически важны для правильного выделения пространства и предотвращения переполнения буфера.”

2. Неправильный расчет длины строки

Ваши циклы для вычисления length1 и length2 имеют ошибки:

c
for (int i = 0; i < 100; i++) {
    if (string1[i] == '\0') {
        // Пустой блок - здесь нужно прервать цикл!
    }
    else {
        length1++;
    }
}

Вы должны прерывать цикл при нахождении нуль-терминатора, чтобы не считать символы за пределами фактической строки.

3. Ошибочная логика удаления символов

Основная проблема находится во вложенных циклах удаления:

c
if (string2[i] == string1[j]) {
    for (int k = j; k < length1 - 1; k++) {
        if (k == length1 - 1) {
            string1[k + 1] = '\0';  // Неправильно! Это устанавливает значение за пределами буфера
        }
        else {
            string1[k] = string1[k + 1];
        }
    }
}

Эта логика имеет несколько проблем:

  • Она изменяет строку во время итерации по ней, что приводит к пропуску символов
  • Обновление нуль-терминатора выполнено неверно
  • Она не правильно обрабатывает случай удаления нескольких символов

Правильный алгоритм удаления строк

Вот правильный подход для удаления символов из строки с поддержкой корректного нуль-терминирования:

c
#include <string.h>  // Для strlen()

void remove_chars(char *str, const char *chars_to_remove) {
    int write_pos = 0;
    int read_pos = 0;
    
    // Читаем исходную строку
    while (str[read_pos] != '\0') {
        // Проверяем, нужно ли оставить текущий символ
        int should_keep = 1;
        for (int i = 0; chars_to_remove[i] != '\0'; i++) {
            if (str[read_pos] == chars_to_remove[i]) {
                should_keep = 0;
                break;
            }
        }
        
        // Оставляем символ, если его нет в списке на удаление
        if (should_keep) {
            str[write_pos] = str[read_pos];
            write_pos++;
        }
        read_pos++;
    }
    
    // Добавляем нуль-терминатор в результат
    str[write_pos] = '\0';
}

Этот алгоритм:

  • Использует отдельные указатели для чтения и записи, чтобы избежать изменения строки во время итерации
  • Правильно поддерживает нуль-терминирование
  • Эффективно обрабатывает удаление нескольких символов

Безопасные методы ввода

Вместо использования scanf("%s"), используйте эти более безопасные альтернативы:

Вариант 1: fgets с указанием размера буфера

c
fgets(string1, sizeof(string1), stdin);
// Удаляем символ новой строки, если он есть
size_t len = strlen(string1);
if (len > 0 && string1[len-1] == '\n') {
    string1[len-1] = '\0';
}

Вариант 2: scanf с указанием ширины

c
scanf("%99s", string1);  // Оставляем место для нуль-терминатора

Как упоминается в уроке LabEx о нуль-терминировании, “Безопасный ввод с ограничением длины: fgets(buffer, sizeof(buffer), stdin); Обеспечение нуль-терминирования: buffer[MAX_BUFFER - 1] = ‘\0’;”

Полное исправленное решение

Вот ваш полный код со всеми исправленными проблемами:

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

void remove_chars(char *str, const char *chars_to_remove) {
    int write_pos = 0;
    int read_pos = 0;
    
    while (str[read_pos] != '\0') {
        int should_keep = 1;
        for (int i = 0; chars_to_remove[i] != '\0'; i++) {
            if (str[read_pos] == chars_to_remove[i]) {
                should_keep = 0;
                break;
            }
        }
        
        if (should_keep) {
            str[write_pos] = str[read_pos];
            write_pos++;
        }
        read_pos++;
    }
    
    str[write_pos] = '\0';
}

int main() {
    char string1[100];
    char string2[100];

    printf("Введите первую строку: ");
    fgets(string1, sizeof(string1), stdin);
    // Удаляем символ новой строки, если он есть
    size_t len1 = strlen(string1);
    if (len1 > 0 && string1[len1-1] == '\n') {
        string1[len1-1] = '\0';
    }

    printf("Введите вторую строку: ");
    fgets(string2, sizeof(string2), stdin);
    // Удаляем символ новой строки, если он есть
    size_t len2 = strlen(string2);
    if (len2 > 0 && string2[len2-1] == '\n') {
        string2[len2-1] = '\0';
    }

    remove_chars(string1, string2);

    printf("Результат: %s\n", string1);
    return 0;
}

Лучшие практики для работы со строками

  1. Всегда обеспечивайте нуль-терминирование: Каждая строка должна заканчиваться '\0'
  2. Используйте безопасные функции ввода: Предпочитайте fgets() или scanf() с указанием ширины
  3. Проверяйте границы буфера: Никогда не записывайте за пределы выделенной памяти
  4. Используйте специализированные строковые функции: Используйте strlen(), strcpy() и т.д., когда это уместно
  5. Инициализируйте строки: Всегда инициализируйте массивы символов, чтобы избежать мусорных значений

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


Заключение

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

  1. Всегда поддерживайте правильное нуль-терминирование - это основная причина ваших мусорных символов
  2. Используйте безопасные методы ввода - избегайте scanf("%s") без проверки границ
  3. Реализуйте правильные алгоритмы удаления - используйте отдельные указатели для чтения и записи, чтобы избежать проблем с итерацией
  4. Тестируйте граничные случаи - всегда тестируйте строки максимальной длины и строки, из которых удаляются все символы
  5. Используйте стандартные библиотечные функции - такие функции, как strlen(), могут упростить ваш код

Следуя этим практикам, вы устраните мусорные символы и создадите более надежный код для работы со строками в C. Предоставленное исправленное решение будет корректно удалять символы из первой строки, поддерживая правильное завершение строки.


Источники

  1. Переполнение буфера - Википедия
  2. Нуль-терминированная строка - Википедия
  3. Безопасное программирование на C и C++: Строки и переполнение буфера | InformIT
  4. Как обеспечить нуль-терминирование строки | LabEx
  5. r/C_Programming на Reddit: Нулевой символ ‘\0’ и нуль-терминированные строки
  6. Stack Overflow: c - как избавиться от мусора в массиве символов?
  7. Stack Overflow: c - Удаление нуль-терминатора в конце строки
Авторы
Проверено модерацией
Модерация