簡體   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