【问题标题】:Continuous string replacement causes invalid read in C连续字符串替换导致 C 中的无效读取
【发布时间】:2020-02-22 00:47:03
【问题描述】:

我正在尝试在 C 中实现 replace 函数。

我有 replace_str(char *str, char *sub, char *replacement) 函数将 char * 返回到修改后的字符串。

以下是函数:

#define ALLOC(size, type) (type *) calloc(size, sizeof(type))
#define REALLOC(ptr, new_size, type) (type *) realloc(ptr, new_size * sizeof(type))

/**
 * Replaces all occurrences of given substring in string with given replacement.
 * @param str A dynamically allocated string that will be modified.
 * @param sub Old substring to be replaced.
 * @param replacement New substring.
 * @return The modified string.
 */
char *replace_str(char *str, char *sub, char *replacement) {
    unsigned long len_sub = strlen(sub);
    unsigned long len_rep = strlen(replacement);

    if (len_sub != len_rep) {
        unsigned long new_len = strlen(str) + (len_rep - len_sub) * count_substring(str, sub) + 1;
        str = REALLOC(str, new_len, char);
    }

    char *ptr = strstr(str, sub);
    while (ptr != NULL) {
        char *str_tail = ptr + len_sub;
        char *temp_tail = ALLOC(strlen(str_tail) + 1, char);
        strcpy(temp_tail, str_tail);
        strcpy(ptr, replacement);
        strcat(str, temp_tail);
        free(temp_tail);
        ptr = strstr(ptr + len_rep, sub);
    }

    return str;
}

/* Counts number of non-overlapping times given substring mentioned in string. */
unsigned int count_substring(char *str, char *sub) {
    unsigned int count = 0;
    unsigned long sub_len = strlen(sub);

    while (*str != '\0') {
        if (strncmp(str++, sub, sub_len) != false)
            continue;
        str += sub_len - 1;
        count++;
    }

    return count;
}

效果很好,但是当我有这样的事情时:

char *a = ALLOC(11, char);
strcpy(a, "\n\n\n\n\n\n\n\n\n\n");

while (CONTAINS(a, "\n\n")) {
    a = replace_str(a, "\n\n", "\n");
}

free(a);

Valgrind 抱怨读取无效:

==896== Memcheck, a memory error detector
==896== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==896== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==896== Command: /mnt/d/archive/education/hu/assignments/fall2019/assignment1/cmake-build-debug/matrixman ../inputs/arrays ../inputs/IO/commands1.txt output1.txt
==896==
==896== Invalid read of size 1
==896==    at 0x4C32D04: strlen (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==896==    by 0x1091F2: replace_str (strutils.h:140)
==896==    by 0x109496: main (main.c:33)
==896==  Address 0x522d096 is 0 bytes after a block of size 6 alloc'd
==896==    at 0x4C31D2F: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==896==    by 0x1091B7: replace_str (strutils.h:134)
==896==    by 0x109496: main (main.c:33)
==896==
{
   <insert_a_suppression_name_here>
   Memcheck:Addr1
   fun:strlen
   fun:replace_str
   fun:main
}
==896== Invalid read of size 1
==896==    at 0x4C32E03: strcpy (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==896==    by 0x10921A: replace_str (strutils.h:141)
==896==    by 0x109496: main (main.c:33)
==896==  Address 0x522d096 is 0 bytes after a block of size 6 alloc'd
==896==    at 0x4C31D2F: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==896==    by 0x1091B7: replace_str (strutils.h:134)
==896==    by 0x109496: main (main.c:33)
==896==
{
   <insert_a_suppression_name_here>
   Memcheck:Addr1
   fun:strcpy
   fun:replace_str
   fun:main
}
==896== Invalid read of size 1
==896==    at 0x4C32CF2: strlen (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==896==    by 0x1091F2: replace_str (strutils.h:140)
==896==    by 0x109496: main (main.c:33)
==896==  Address 0x522d272 is 0 bytes after a block of size 2 alloc'd
==896==    at 0x4C31D2F: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==896==    by 0x1091B7: replace_str (strutils.h:134)
==896==    by 0x109496: main (main.c:33)
==896==
{
   <insert_a_suppression_name_here>
   Memcheck:Addr1
   fun:strlen
   fun:replace_str
   fun:main
}
==896== Invalid read of size 1
==896==    at 0x4FB7950: __strcpy_ssse3 (strcpy-ssse3.S:32)
==896==    by 0x10921A: replace_str (strutils.h:141)
==896==    by 0x109496: main (main.c:33)
==896==  Address 0x522d272 is 0 bytes after a block of size 2 alloc'd
==896==    at 0x4C31D2F: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==896==    by 0x1091B7: replace_str (strutils.h:134)
==896==    by 0x109496: main (main.c:33)
==896==
{
   <insert_a_suppression_name_here>
   Memcheck:Addr1
   fun:__strcpy_ssse3
   fun:replace_str
   fun:main
}
==896==
==896== HEAP SUMMARY:
==896==     in use at exit: 0 bytes in 0 blocks
==896==   total heap usage: 9 allocs, 9 frees, 34 bytes allocated
==896==
==896== All heap blocks were freed -- no leaks are possible
==896==
==896== For counts of detected and suppressed errors, rerun with: -v
==896== ERROR SUMMARY: 6 errors from 4 contexts (suppressed: 0 from 0)

当我在递归函数中使用replace_str 时也会发生这种情况。

我该如何解决这个问题?我没看到什么?

【问题讨论】:

  • 您在处理之前将字符串重新分配到更小的尺寸...

标签: c string loops


【解决方案1】:

下面我使用 needle 术语,意思是 sub 参数传递给 replace_str 函数。

unsigned long new_len = strlen(str) + (len_rep - len_sub) * count_substring(str, sub) + 1;
str = REALLOC(str, new_len, char);

len_repstrlen("\n"),所以它是 1
len_substrlen("\n\n"),所以它是 2

所以(len_rep - len_sub)-1,所以它等于:

unsigned long new_len = strlen(str) + (-1) * count_substring(str, sub) + 1;

假设count_substring 做了它所说的,你realloc 甚至在访问它之前将字符串变小,所以你使new_len 之后的所有字节无效。这意味着如果字符串是"\n\n\n\n\n\n\n\n\n\n" 而needle 是"\n\n",则有5 个这样的序列,您只需将字符串重新分配为6 个字节。所以结果数组只是\n6 字节:{'\n,'\n','\n','\n','\n','\n'}。索引 5 之后的所有字节都是无效的,访问它们是未定义的行为。 Valgrind 完全错误你在分配内存后访问字节:

==896==  Address 0x522d096 is 0 bytes after a block of size 6 alloc'd
==896==    at 0x4C31D2F: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

你应该做的是,如果替换字符串比针短,则在循环之后移动 realloc。如果替换比针长,请在循环之前执行 realloc。

if (len_rep > len_sub) {
    // WE NEED MORE PLACE
    const size_t new_len = strlen(str) + (len_rep - len_sub) * count_substring(str, sub) + 1;
    str = REALLOC(str, new_len, char);
    if (str == NULL) abort();
}

while (strstr(str, needle)) {
    // your loop is here
}

if (len_rep < len_sub) {
    // WE NEED LESS PLACE
    const size_t new_len = strlen(str) + (len_rep - len_sub) * count_substring(str, sub) + 1;
    str = REALLOC(str, new_len, char);
    if (str == NULL) abort();
}

注意事项:

  • 记得检查分配错误。
  • 对象大小和字符串长度使用size_t 类型表示。
  • 您的函数工作异常。在while 内几乎不需要做malloc,你不需要临时字符串。只需memove 您必须保存的字符串部分。和memcpy 针在你需要的地方。就像while (str = strstr(str, needle), str) { memmove( &lt;move str forth or back depending if len_rep is greater or smaller or equal to len_sub&gt; ); memcpy(str, needle, strlen(needle)); str += strlen(needle); }

【讨论】:

  • 非常感谢!有用。在您详细解释之前和看到您的评论之后,我想出了在循环后缩小字符串的相同解决方案,但我想知道是否有比拆分 realloc 操作更优雅的解决方案。我想没有 this 替换算法。再次感谢您如此详细的解释!
猜你喜欢
  • 2021-03-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-31
  • 2018-04-12
  • 2021-12-05
  • 2010-11-28
  • 1970-01-01
相关资源
最近更新 更多