子串替换和memcpy

时间:2017-06-22 00:40:34

标签: c string

根据这个问题here中的建议,我编写了以下程序来查找并替换字符串中的目标子字符串。 (它可以在循环中运行,以使用strstr()字符串方法查找和替换“target”的所有实例,但我在这里举一个示例。)

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

#define SUCCESS 1
#define FAILURE 0
#define STR_CAP 80 + 1

int stringify(char *string, const char *target, const char *replacement) {
    char segment[STR_CAP];
    int S = strlen(string), T = strlen(target);
    char pre_segment[STR_CAP];
    char post_segment[STR_CAP];

    for (int i = 0; i < S; i++) {
        strncpy(segment, string + i, T);
        segment[T] = '\0';
        if (strcmp(segment, target) == 0) {
            memcpy(pre_segment, string, i);
            /*printf("pre_segment: %s\n", pre_segment);*/
            pre_segment[i] = '\0';
            memcpy(post_segment, string + i + T, S - (i + T));
            post_segment[S - (i + T)] = '\0'; /*WHAT IS THIS MAGIC?*/
            /*printf("post_segment: %s\n", post_segment);*/     
            strcat(pre_segment, replacement);
            /*printf("pre_segment after concat.: %s\n", pre_segment);*/
            strcat(pre_segment, post_segment);
            /*printf("pre_segment after concat,: %s\n", pre_segment);*/
            strcpy(string, pre_segment);
            return SUCCESS;
        }   
    } return FAILURE;
}

int main() {

    char string[] = "The quick brown fox jumped over the lazy, brown dog.";
    char target[] = "brown";
    char replacement[] = "ochre-ish";

    stringify(string, target, replacement);
    printf("%s\n", string);
    stringify(string, target, replacement);
    printf("%s\n", string);

    return 0;

}

但有一些我不明白的事情。

(1)一方面:在我使用memcpy()并手动设置我复制到'\0'的字符串的末尾之前,我一直得到缓冲区溢出(以堆栈粉碎的形式) ,以及奇怪的错误。这有什么影响,只是天真地使用strncpy()不?例如,如果您注释掉“MAGIC”行,那么程序就会崩溃。

(2)这样做有更好或更规范的方法吗?这感觉非常笨重,但我找不到任何关于子串替换的基本“how-tos”。

3 个答案:

答案 0 :(得分:1)

  1. memcpy()仅复制内存,并且没有字符串和空终止符的重新收集。 strncpy()将复制前n个字符,并且只有在n个字符中找到空终止符时才会用0(空终止符)填充其余字符,所以你有可能给strncpy()字符串的长度{}想要复制,它做了,但没有找到一个空终止符。从这个意义上说,strncpy()就像memcpy()一样。无论哪种方式,您都需要手动分配空终止符。 内存被损坏只是因为程序读取了一个字符串,但没有找到空终止符,因此将继续读取,直到它经过堆栈,堆和内存的其余部分,直到它到达空终止符(不是那个顺序)。

  2. 有很多方法可以进行子串替换,这是另一种方法,它更优雅:What is the function to replace string in C?

  3. 编辑回应:“你能解释一下memcpy()的作用以及人们对strncpy()如此不满意的原因吗?”

    想象一下你的记忆如下:

    0              3    
    01 | 30 | 40 | 00 | 32 | 40 
    

    然后执行memcpy(0x03, 0x00, 3);告诉计算机将3个字节从地址0复制到地址3.现在我的内存看起来像这样:

    0              3    
    01 | 30 | 40 | 01 | 30 | 40 
    

    计算机完全按照我的指示逐字节复制。让我们来看看strncpy()在这种情况下会做什么。从上面的第一个图开始,调用strncpy(0x03, 0x00, 3);告诉计算机将空终止的char数组(字符串)复制到0x00,然后复制到3个字符。结果是:

    0              3    
    01 | 30 | 40 | 01 | 30 | 40 
    

    让我们想象一下,我们开始时:

    0              3    
    65 | 00 | 40 | 01 | 30 | 40 
    

    这里,地址0x00实际上是指1个字符长的字符串:“A”。 现在,如果我们调用相同的strncpy(0x03, 0x00, 3)计算机首先复制65,请注意空终止符,然后停在那里。然后用0填充其余部分,结果如下:

    0              3    
    65 | 00 | 40 | 65 | 00 | 00
    

    您可以看到,如果您的字符串超过3个字符,strncpy将不会为您复制空终结符,因为它将“最大化”。这与memcpy相同。如果在前n个字节中遇到空终止符,strncpy将仅将空终止符移动到目标。因此,您可以在前几个示例中看到,即使地址0x00包含3个字符的有效字符串(在地址0x03处以0结尾),strncpy()函数也未在目标(0x03)处生成有效字符串,因为0x04可以是任何东西(不一定是空终止符)。

答案 1 :(得分:1)

memcpy()只是将一个字节从一个数组复制到另一个数组。这是一个非常简单的功能,有两个注意事项:

  • 当源和目标数组重叠时,不应使用它。
  • 它意味着复制字节,因此大小必须计算为多个字节,这在大型类型的数组之间复制时容易出错。在这种情况下,char类型根据定义恰好是一个字节,因此大小只是字符数,如果需要,还为空终止符加1。

memmove()memcpy的作用相同,但它可用于重叠对象。它需要额外的测试来正确处理所有情况。

使用这些函数,这是stringify函数的更简单版本:

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

int stringify(char *dest, const char *target, const char *replace) {
    char *p = strstr(dest, target);
    if (p == NULL) {
        /* no replacement */
        return 0;
    }
    size_t len1 = strlen(target);
    size_t len2 = strlen(replace);
    if (len1 != len2) {
        /* move the remainder of the string into the right place */
        memmove(p + len2, p + len1, strlen(p + len1) + 1);
    }
    memcpy(p, replace, len2);
    return 1;
}

int main(void) {
    char string[80] = "The quick brown fox jumped over the lazy, brown dog.";
    char target[] = "brown";
    char replacement[] = "ochre-ish";

    stringify(string, target, replacement);
    printf("%s\n", string);
    stringify(string, target, replacement);
    printf("%s\n", string);

    return 0;
}

注意:

  • 当替换长度超过目标字符串时,此函数不会检查目标数组是否有足够的空间。
  • 在您的代码中,目标字符串不够长,产生缓冲区溢出,导致未定义的行为。
  • 必须使用
  • memmove代替memcpy来移动字符串的其余部分,因为源和目标数组重叠。
  • 如果目标和替换字符串有一个共同的部分,则迭代地调用stringify可能无法产生预期的替换:将brown替换为brownish显然会因多次匹配而失败。

替换循环中的所有匹配项,并分配生成的字符串并不困难:

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

char *replace_all(const char *str, const char *target, const char *replace) {
    size_t len1 = strlen(target);
    size_t len2 = strlen(replace);
    size_t matches = 0;
    const char *p, *p0;
    char *dest, *q;

    if (len1 > 0) {
        for (p = str; p = strstr(p, target) != NULL; p += len1)
            matches++;
    }
    if (matches == 0) {
        return strdup(str);
    }
    dest = malloc(strlen(str) - len1 * matches + len2 * matches + 1);
    if (dest != NULL) {
        for (p = str, q = dest; (p = strstr(p0 = p, target)) != NULL; p += len1) {
            memcpy(q, p0, p - p0);
            q += p - p0;
            memcpy(q, replace, len2);
            q += len2;
        }
        memcpy(q, p0, strlen(p0) + 1);
    }
    return dest;       
}

int main(void) {
    char string[] = "The quick brown fox jumped over the lazy, brown dog.";
    char target[] = "brown";
    char replacement[] = "brownish";

    char *p = replace_all(string, target, replacement);
    printf("%s\n", p);
    free(p);

    return 0;
}

答案 2 :(得分:0)

您的方法存在一个大问题:replacement可能足够长 结果字符串的长度超过80个字符。

我这样做

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

char *stringify(const char *src, const char *target, const char *replacement)
{
    int buffer_len = 0;
    char *buffer = NULL, *tmp;
    if(src == NULL || replacement == NULL || target == NULL)
        return NULL;

    int repl_len = strlen(replacement);
    int target_len = strlen(target);

    while(tmp = strstr(src, target) && target_len)
    {
        // get length of string until target
        int substr_len = tmp - src;

        // get more space
        char *new_buff = realloc(buffer, buffer_len + substr_len + repl_len + 1);

        if(new_buff == NULL)
        {
            // out of memory
            return buffer;
        }

        if(buffer == NULL) // first realloc
            *new_buff = 0;

        buffer = new_buff;

        // copy that what is before the found target
        strncat(buffer, src, substr_len);

        buffer[buffer_len + substr_len] = 0;

        //// copy the replacement at the end of buffer
        strncat(buffer, replacement, repl_len);

        buffer[buffer_len + substr_len + repl_len] = 0;

        buffer_len += substr_len + repl_len;

        //// set src to the next character after the replacement
        src = tmp + target_len;
    }

    if(buffer == NULL) // target never found
    {
        buffer = malloc(strlen(src) + 1);
        if(buffer == NULL)
            return NULL;

        strcpy(buffer, src);
    } else {
        if(*src) {
            // copy the leftover
            int leftover_len = strlen(src);

            char *new_buff = realloc(buffer, buffer_len + leftover_len + 1);

            if(new_buff == NULL)
                return buffer; // could not expand more, return all we've converted

            buffer = new_buff;

            strcat(buffer, src);
        }
    }

    return buffer;
}


int main(void)
{
    char string[] = "The quick brown fox jumped over the lazy, brown dog.";
    char target[] = "brown";
    char replacement[] = "ochre-ish";

    char *new_txt = stringify(string, target, replacement);

    if(new_txt)
    {
        printf("new string: %s\n", new_txt);
        free(new_txt);
    } else
        printf("Out of memory\n");

    return 0;
}

当然你可以改进代码,但我的代码应该显示如何使用 realloc和指针算术在你开始时非常重要 操纵字符串或数据数组。