Почему я получаю переполнение буфера кучи в leetcode?

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

char *longestPalindrome(char *s) {
    int len = strlen(s);
    char c[len];
    for (int i = 0; i < len; i++) {
        c[i] = s[len - 1 - i];
    }
    int st = 0;
    int length = 0;
    for (int i = 0; i < len; i++) {
        for (int j = 0; j < len; j++) {
            int l = 0;
            for (int k = 0; ((i + k) < len) && ((j + k) < len); k++) {
                if (s[i + k] == c[j + k])
                    l++;
                else
                    break;
            }
            if (l > length) {
                length = l;
                st = i;
            }
        }
    }
    char *ans = (char *)calloc(length, sizeof(char));
    for (int i = 0; (i < length) && (i + st < len); i++) {
        ans[i] = s[i + st];
    }
    return ans;  
}

Я продолжаю получать эту ошибку:

ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000033
  at pc 0x559567cee1ab bp 0x7ffdab22ea70 sp 0x7ffdab22ea60

Теперь, когда я комментирую условия if-else внутри третьего цикла for, я не получаю никаких ошибок. Почему это происходит? Несмотря на добавление условий (i + k) < len и (j + k) < len?

Я попытался закомментировать условие if-else, и код не выдает ошибки.

скомпилировать с -wall

Mitch Wheat 20.07.2023 08:08

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

Some programmer dude 20.07.2023 08:09

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

Elec1 20.07.2023 08:12

Я попробую скомпилировать и отладить с помощью «-wall», а также посмотреть на случай «\ 0». Спасибо за предложения :)

DhruvS 20.07.2023 09:08

Примечание: не используйте результат malloc() и семьи. Эти функции выделения памяти возвращают общий void *, который неявно преобразуется в любой другой указатель, например. Приведение типов является излишним и просто служит для загромождения кода.

Harith 20.07.2023 09:22

Вы уверены, что сообщение об ошибке исходит из опубликованного кода? Я так не думаю.

Support Ukraine 20.07.2023 09:23

Этот вариант должен быть -Wall. И вы также должны добавить -Wextra -pedantic

Gerhardh 20.07.2023 09:25

@Gerhardh Если предположить, что «настоящий» код включает stdlib.h, для этого кода не будет предупреждений.

Support Ukraine 20.07.2023 09:29
Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
1
9
82
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Похоже, вам просто нужно завершить строку результата перед ее печатью. Просто измените calloc(length, ... на calloc(length + 1, ....

Опубликованный код не выдаст ошибку, указанную в вопросе. В опубликованном коде нет доступа за пределы.

Другими словами, ошибка возникает из-за кода, который не опубликован. Возможно/вероятно, вызывающая сторона longestPalindrome использует возвращенный указатель ans таким образом, что это вызывает опубликованную ошибку.

Ключ здесь:

...возвращение самого длинного палиндрома в строке.

Ваш код не возвращает строку стиля C. Выделенная память не содержит символ завершения строки (завершение NUL). (примечание: в настоящее время даже нет места для завершения в выделенной памяти)

Итак, если caller сделать что-то вроде:

...
char* str = longestPalindrome(someString);
if (str) puts(str);

вызов puts будет обращаться к памяти за пределами границ.

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

Также код не обрабатывает случай, когда ввод является пустой строкой. Это также может привести к ошибкам.

Кстати: будьте осторожны с использованием VLA, таких как char c[len];. Если входная строка очень большая, это может привести к переполнению стека.

Хорошее замечание о VLA

chqrlie 21.07.2023 15:58

Интересный побочный вопрос: можно ли решить эту задачу за линейное время?

chqrlie 21.07.2023 16:00
Ответ принят как подходящий

В коде есть несколько проблем:

  • вы должны выделить один дополнительный байт для совпадения и сохранить нулевой терминатор в конце блока, чтобы сделать его правильной строкой C:

      char *ans = calloc(length + 1, sizeof(char));
      for (int i = 0; i < length; i++) {
          ans[i] = s[st + i];
      }
      ans[length] = '\0';
      return ans;  
    

    обратите внимание, что вы также можете использовать strndup() и заменить весь блок кода на

      return strndup(st + i, length);
    

    без ограничителя null вызывающий код вызовет доступ за пределы, поскольку он пытается найти конец строки, например, при ее печати.

  • Алгоритм не находит встроенные палиндромы в строке аргумента, он находит подстроки, которые имеют обратную версию, встроенную где-то в строку аргумента. Например, for "a dog has no god" вернет "dog ", что не является палиндромом.

Вот модифицированная версия:

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

char *longestPalindrome(const char *s) {
    size_t len = strlen(s);
    size_t st = 0;
    size_t length = 0;
    size_t i, n, k;
    for (i = 0; i + length < len; i++) {
        for (n = length + 1; i + n < len; n++) {
            for (k = 0; k < n; k++) {
                if (s[i + k] != s[i + n - 1 - k])
                    break;
            }
            if (k == n) {
                /* found a palindrome of length n */
                length = n;
                st = i;
            }
        }
    }
    return strndup(s + st, length);
}

int main(int argc, char *argv[]) {
    for (int i = 1; i < argc; i++) {
        char *p = longestPalindrome(argv[i]);
        printf("'%s' -> '%s'\n", argv[i], p);
        free(p);
    }
    return 0;
}

Функции strdup и strndup долгое время были частью стандарта POSIX, и, наконец, они были включены в новый стандарт C23. Если strndup() недоступен в вашей системе, его можно записать так:

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

char *strndup(const char *s, size_t n) {
    char *p = malloc(n + 1);
    if (p != NULL) {
        memcpy(p, s, n);
        p[n] = '\0';
    }
    return p;
}

Хороший ответ. AFAIK strndup (все еще) нестандартен. Пожалуй, стоит упомянуть.

Support Ukraine 20.07.2023 10:27

@SupportUkraine: Хорошие новости: функция POSIX strndup наконец-то вошла в следующий стандарт C: C23 7.26.2.7 Функция strndup.

chqrlie 20.07.2023 18:23

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

Support Ukraine 20.07.2023 19:32

Другие вопросы по теме