繁体   English   中英

如何替换字符串中的 substring?

[英]How to replace a substring in a string?

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

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. 为什么我有 memory 损坏?

  2. 如何使用new_sub_s有效提取和替换pattern

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

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

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

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

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

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

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

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

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

在对新分配的 memory 区域进行任何操作之前,使其包含空字符串:将第一个字符设置为 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, …);

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

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

#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;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM