【问题标题】:How to replace a substring in a string?如何替换字符串中的子字符串?
【发布时间】:2020-08-18 15:07:47
【问题描述】:

我有一个字符串,我需要在其中找到一个子字符串并替换它。要找到的和将替换它的长度不同。我的部分代码:

char *source_str = "aaa bbb CcCc dddd kkkk xxx yyyy";
char *pattern = "cccc";
char *new_sub_s = "mmmmm4343afdsafd";

char *sub_s1 = strcasestr(source_str, pattern);

printf("sub_s1: %s\r\n", sub_s1);
printf("sub_str before pattern: %s\r\n", sub_s1 - source_str); // Memory corruption

char *new_str = (char *)malloc(strlen(source_str) - strlen(pattern) + strlen(new_sub_s) + 1);

strcat(new_str, '\0');
strcat(new_str, "??? part before pattern ???");
strcat(new_str, new_sub_s);
strcat(new_str, "??? part after pattern ???");
  1. 为什么会出现内存损坏?

  2. 如何有效提取pattern并将new_sub_s替换为new_sub_s

【问题讨论】:

  • 最好先看这里geeksforgeeks.org/…
  • @user3121023 我有这个strcat(new_str, '\0');
  • @kosmosu 两个问题,strcat() 假设 new_str 是空终止的,正如 user3121023 指出的那样,malloc() 不是这种情况。其次,'\0'char,而不是 char*

标签: c


【解决方案1】:

您的代码中存在多个问题:

  • 您不测试是否在字符串中找到sub_s1。如果没有匹配怎么办?
  • printf("sub_str before pattern: %s\r\n", sub_s1 - source_str); 传递了 %s 的指针差异,它需要一个字符串。行为未定义。
  • strcat(new_str, '\0'); 具有未定义的行为,因为目标字符串未初始化,并且您将空指针作为要连接的字符串传递。 strcat 需要一个字符串指针作为它的第二个参数,而不是 char,而 '\0' 是一个类型为 int(在 C 中)和值 0 的字符常量,编译器会将其转换为空指针,有或没有警告。你可能打算写*new_str = '\0';

你不能用strcat 组成新字符串:因为匹配前的字符串不是C 字符串,它是C 字符串的片段。您应该改为确定源字符串不同部分的长度,并使用memcpy 复制具有明确长度的片段。

这是一个例子:

char *patch_string(const char *source_str, const char *pattern, const char *replacement) {
    char *match = strcasestr(source_str, pattern);
    if (match != NULL) {
        size_t len = strlen(source_str);
        size_t n1 = match - source_str;   // # bytes before the match
        size_t n2 = strlen(pattern);      // # bytes in the pattern string
        size_t n3 = strlen(replacement);  // # bytes in the replacement string
        size_t n4 = len - n1 - n2;        // # bytes after the pattern in the source string
        char *result = malloc(n1 + n3 + n4 + 1);
        if (result != NULL) {
            // copy the initial portion
            memcpy(result, source_str, n1);
            // copy the replacement string
            memcpy(result + n1, replacement, n3);
            // copy the trailing bytes, including the null terminator
            memcpy(result + n1 + n3, match + n2, n4 + 1);
        }
        return result;
    } else {
        return strdup(source_str);  // always return an allocated string
    }
}

请注意,上面的代码假定源字符串中的匹配项与模式字符串具有相同的长度(在示例中,字符串"cccc""CcCc" 具有相同的长度)。鉴于 strcasestr 预计将执行不区分大小写的搜索,这已由问题中的示例字符串确认,因此该假设可能会失败,例如,如果大小写字母的编码具有不同的长度,或者如果重音与strcasestr 匹配,正如法语中所预期的那样:"é""E" 应该匹配,但在以 UTF-8 编码时具有不同的长度。如果strcasestr 具有这种高级行为,那么如果没有更复杂的 API,就无法确定源字符串匹配部分的长度。

【讨论】:

  • You cannot compose the new string with strcat as posted. 为什么不呢?
  • @kosmosu:我用更多解释扩展了答案。
  • 不知道这是否只是一个疏忽,但strcat(new_str, '\0'); 甚至不应该编译。
  • @PatrickRoberts: '\0' 是一个字符常量,类型为int,值为0,因此如果strcat() 的原型在范围内,则转换为空字符指针。这非常令人困惑,并且可能会被编译器标记为警告。我会更明确地说明这个错误。
  • @chqrlie 你说得对,我忘了那把特别的脚枪......
【解决方案2】:
printf("sub_str before pattern: %s\r\n", sub_s1 - source_str); // Memory corruption

您正在获取两个指针的差异,并将其打印为就好像它是指向字符串的指针一样。实际上,在您的机器上,这可能会计算出一个无意义的数字并将其解释为内存地址。由于这是一个很小的数字,当在您的系统上解释为地址时,这可能指向未映射的内存,因此您的程序崩溃。根据平台、编译器、优化设置、程序中的其他内容以及月球的相位,任何事情都可能发生。我是undefined behavior

任何半体面的编译器都会告诉您%s 指令和参数之间存在类型不匹配。打开这些警告。例如,使用 GCC:

gcc -Wall -Wextra -Werror -O my_program.c
char *new_str = (char *)malloc(…);
strcat(new_str, '\0');
strcat(new_str, "…");

strcat 的第一次调用尝试附加'\0'。这是一个字符,而不是字符串。碰巧因为这是字符0,而C不区分字符和数字,这只是整数0的一种奇怪的写法。任何值为 0 的整数常量都是写入空指针常量的有效方式。所以strcat(new_str, '\0') 等价于strcat(new_str, NULL),它可能会因为试图取消引用空指针而崩溃。根据编译器的优化,编译器可能会认为这段代码永远不会执行,因为它试图取消引用空指针,这是未定义的行为:就编译器而言,这不可能发生.在这种情况下,您可以合理地预期未定义的行为会导致编译器执行一些看起来很荒谬但从编译器看待程序的方式来看完全合理的事情。

即使您按照您的意图写了strcat(new_str, "\0"),那也毫无意义。请注意,"\0" 是写 "" 的一种毫无意义的方式:字符串文字的末尾总是有一个空终止符¹。并且将一个空字符串附加到一个字符串不会改变它。

strcat 调用还有另一个问题。此时new_str的内容还没有初始化。但是strcat(如果调用正确,即使是strcat(new_str, ""),如果编译器没有优化它)将探索这个未初始化的内存并寻找第一个空字节。由于内存未初始化,因此无法保证分配的内存中存在空字节,因此strcat 可能会在缓冲区用完时尝试从未映射的地址读取,或者它可能会损坏任何东西。或者它可能会让恶魔从你的鼻子里飞出来:这又是未定义的行为。

在对新分配的内存区域进行任何操作之前,请使其包含空字符串:将第一个字符设置为 0。在此之前,检查 malloc 是否成功。它在你的玩具程序中总是会成功,但在现实世界中却不会。

char *new_str = malloc(…);
if (new_str == NULL) {
    return NULL; // or whatever you want to do to handle the error
}
new_str[0] = 0;
strcat(new_str, …);

¹ "…" 的末尾没有空指针的唯一情况是当您使用它来初始化一个数组并且拼写出来的字符填充整个数组而没有留出空间的时候空终止符。

【讨论】:

  • And there's another problem with the strcat calls. At this point, the content of new_str is not initialized. --> 我应该用 calloc 代替吗?
  • @kosmosu 你可以,是的。如果您知道自己在做什么,使用malloc 并初始化第一个字节就足够了。使用calloc 会稍微慢一些,但这并不重要,而且更安全。我处理安全关键代码,我们禁止malloc,我们只允许calloc。其他项目,尤其是分配非常大的内存块的项目,有不同的规则。
【解决方案3】:

snprintf 可用于计算所需的内存,然后将字符串打印到分配的指针。

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

int main ( void) {
    char *source_str = "aaa bbb CcCc dddd kkkk xxx yyyy";
    char *pattern = "cccc";
    char *new_sub_s = "mmmmm4343afdsafd";

    char *sub_s1 = strcasestr(source_str, pattern);
    int span = (int)( sub_s1 - source_str);
    char *tail = sub_s1 + strlen ( pattern);

    size_t size = snprintf ( NULL, 0, "%.*s%s%s", span, source_str, new_sub_s, tail);

    char *new_str = malloc( size + 1);

    snprintf ( new_str, size, "%.*s%s%s", span, source_str, new_sub_s, tail);

    printf ( "%s\n", new_str);

    free ( new_str);

    return 0;
}

【讨论】:

  • GNU 系统也有asprintf(),这是一个更直接的解决方案。
猜你喜欢
  • 2020-04-05
  • 2012-04-03
  • 2013-07-23
  • 2014-06-09
  • 2015-11-13
  • 1970-01-01
相关资源
最近更新 更多