如何修复strcpy以便检测重叠的字符串

时间:2011-09-15 08:01:44

标签: c overlap strcpy

在一次采访中,我被要求编写strcpy的实现,然后修复它以便正确处理重叠的字符串。我的实现如下,非常天真。我该如何修复它:

  1. 检测重叠的字符串和
  2. 检测后,我们如何处理重叠并继续?

  3. char* my_strcpy(char *a, char *b) {
    
         if (a == NULL || b == NULL) {
             return NULL;
         }
         if (a > b) {
             //we have an overlap?
             return NULL;
         }
         char *n = a;
    
         while (*b != '\0') {
             *a = *b;
             a++;
             b++;
         }
         *a = '\0';
         return n;
    }
    
    int main(int argc, char *argv[])
    {
        char str1[] = "wazzupdude";
        char *after_cpy = my_strcpy(str1 + 2, str1);
        return 0;
    }
    

    修改

    因此,基于 @ Secure的答案的一种可能的实现是:

    char* my_strcpy(char *a, char *b) {
    
        if (a == NULL || b == NULL) {
            return NULL;
        }
    
        memmove(a, b, strlen(b) + 1);
        return a;
    }
    

    如果我们不依赖memmove,那么

    char* my_strcpy(char *a, char *b) {
    
        if (a == NULL || b == NULL) {
            return NULL;
        }
    
        if (a == b) {
            return a;
        }
    
        // case1: b is placed further in the memory
        if ( a <= b && a + strlen(a) > b ) {
            char *n = a;
    
            while(*b != '\0') {
                *a = *b;
                a++; b++;
            }
            *a = '\0';
            return n;
        }
    
        // case 2: a is further in memory
        else if ( b <= a && b + strlen(b) > a ) { 
            char *src = b + strlen(b) - 1; // src points to end of b
            char *dest = a;
    
            while(src != b) {
                *dest = *src;
                dest--; src--;  // not sure about this..
            }
            *a = '\0';
            return a;
        }
    }
    

9 个答案:

答案 0 :(得分:9)

没有可移植的方式来检测这一点。您必须进行指针比较,这些仅在同一对象中定义。即如果两个字符串不重叠并且实际上是不同的对象,则指针比较会给出未定义的行为。

我会让标准库使用memmove(a, b, strlen(b) + 1)来处理这个问题。

编辑:

正如Steve Jessop在评论中指出的那样,在这种情况下,实际上有一种可移植但方式来检测重叠。将b中的每个地址与for的第一个和最后一个地址进行比较。与==的平等比较总是很明确。

所以你有这样的事情:

l = strlen(b);
isoverlap = 0;
for (i = 0; i <= l; i++)
{
    if ((b + i == a) || (b + i == a + l))        
    {
        isoverlap = 1;
        break;
    }
}

编辑2:案例2的可视化

你有类似下面的数组和指针:

S t r i n g 0 _ _ _ _ _ _ _
^       ^
|       |
b       a

请注意,b + strlen(b)会生成指向终止\ 0的指针。开始一个,否则你需要额外处理边缘情况。在那里设置指针是有效的,你只是不能取消引用它们。

src = b + strlen(b) + 1;
dst = a + strlen(b) + 1;

S t r i n g 0 _ _ _ _ _ _ _
^       ^     ^       ^  
|       |     |       |
b       a     src     dst

现在复制循环也会复制\ 0。

while (src > b)
{
    src--; dst--;
    *dst = *src;
}

第一步是:

src--; dst--;

S t r i n g 0 _ _ _ _ _ _ _
^       ^   ^       ^  
|       |   |       |
b       a   src     dst

*dst = *src;

S t r i n g 0 _ _ _ 0 _ _ _
^       ^   ^       ^  
|       |   |       |
b       a   src     dst

依此类推,直到src等于b

S t r i S t r i n g 0 _ _ _
^       ^              
|       |            
b       a          
src     dst

如果你想要它更加强硬,你可以进一步压缩它,但我不推荐这个:

while (src > b)
    *(--dst) = *(--src);

答案 1 :(得分:4)

如果您希望字符串重叠,则可以使用memmove()。

char* my_strcpy(char *a, char *b)
{
    memmove(a, b, strlen(b) + 1);
    return a;
}

答案 2 :(得分:4)

注意:此处,b是源字符串的地址,a是目标地址。

使用a > b,您不一定会有重叠。如果

(a <= b && a+strlen(a) >= b) || (b <= a && b+strlen(b) >= a)

然后你有重叠。

然而,除了为了采访而检测重叠之外,a > b应该对strcpy做得很好。这个想法是这样的:

如果将b放在内存中b > a),那么您通常可以将b复制到ab的部分内容将被覆盖,但您已经超过了该部分。

如果将a放在内存中a > b),则表示可能,写在a的第一个位置,您已经覆盖b中具有更高索引的位置。在这种情况下,您应该以相反的方向复制。因此,您应该从0复制到strlen(b)-1,而不是从索引strlen(b)-1复制到0

如果您对此有何帮助感到困惑,请在纸上绘制两个重叠的数组,并尝试从数组的开头复制一次,然后从结尾复制一次。在a > ba < b的情况下,尝试使用重叠数组。

注意,如果a == b,您不需要实际复制任何内容,只需返回即可。

编辑:我不确定,但阅读其他解决方案,似乎这个答案可能不完全可移植。要小心。

答案 3 :(得分:3)

if a > b; then
    copy a from the beginning
else if a < b; then
    copy a from the ending
else // a == b
    do nothing

你可以参考memmove的{​​{3}},这就像我说的那样。

答案 4 :(得分:1)

if (a>= b && a <= b+strlen(b))) || (b+strlen(b) >= a && b+strlen(b) <= a + strlen(b))

(*)你应该缓存strlen(b)以提高性能

它的作用:
检查a+len [+额外len字节的地址]是否在字符串内,或a [地址]在字符串内,这些是字符串的唯一可能性重叠。

答案 5 :(得分:1)

我在最近的一次采访中被问到这个问题。我们不必'检测'重叠。我们可以用重叠地址处理的方式编写strcpy。关键是从源字符串的末尾而不是从开头复制。

这是一个快速代码。

void str_copy(const char *src, char *dst) 
{
    /* error checks */

    int i = strlen(a); /* may have to account for null character */

    while(i >= 0) 
    {
        dst[i] = src[i];  
        i--; 
    }
}

编辑:这只适用于&lt;湾对于&gt; b,从一开始就复制。

答案 6 :(得分:1)

如果这两个字符串重叠,那么,在复制时,您将覆盖原始的ab指针。

假设strcpy(a,b)大致意味着&lt; -b,即第一个参数是副本的目的地,那么你只检查复制指针是否达到b的位置。 / p>

您只需保存b原始位置,并在复制时检查您是否未到达。此外,如果已到达该位置,请不要写尾随零。

 char* my_strcpy(char *a, const char *b)
 {

    if ( a == NULL
      || b == NULL )
    {
        return NULL;
    }

    char *n = a;
    const char * oldB = b;

    while( *b != '\0'
       &&  a != oldB )
    {
        *a = *b;
        a++;
        b++;
    }

    if ( a != oldB ) {
        *a = '\0';
    }

    return n;
 }

此算法只是停止复制。也许你想做其他的事情,比如标记错误条件,或者将字符串标记的末尾添加到前一个位置(虽然默默地失败(因为算法目前做的)并不是最好的选择)。

希望这有帮助。

答案 7 :(得分:1)

即使不使用关系指针比较,memmove或等效,也可以对strcpy的版本进行编码,该版本将作为strlenmemcpy执行不重叠的情况,以及重叠情况下的自上而下的副本。关键是利用如下事实:如果读取目标的第一个字节然后替换为零,则在源上调用strlen并添加到源指针,返回的值将产生一个合法的指针,等于“麻烦重叠”情况下目的地的起点。如果源和目标是不同的对象,则可以安全地计算“source plus strlen”指针并观察它们与目标不相等。

如果将字符串长度添加到源指针产生目标指针,则使用较早读取的值替换零字节并在目标上调用strlen将允许代码确定源字符串和目标字符串的结束地址。此外,源字符串的长度将指示指针之间的距离。如果此值很大(可能大于16左右),代码可以有效地将“移动”操作细分为自上而下的memcpy操作序列。否则,可以使用自上而下的单字节复制操作循环复制字符串,也可以使用“memcpy to source to buffer”/“memcpy buffer to destination”操作[如果大字节的每字节成本]进行复制不到单个字符复制循环的一半,使用~256字节的缓冲区可能是一个有用的优化]。

答案 8 :(得分:0)

该SO条目已经很旧,但是我目前正在处理一段旧代码,该代码使用self.var2 = tk.StringVar(master=self.book_win) 复制重叠的字符串。日志输出中缺少字符。我决定使用以下紧凑型解决方案,该方案将strcpy()复制到char

char

编辑:

正如@Gerhardh指出的那样,以上代码仅在static char *overlapped_strcpy(char *dest, const char *src) { char *dst = dest; if (dest == NULL || src == NULL || dest == src) return dest; do { *dst++ = *src; } while (*src++); return dest; } 下有效(我只需要解决这种情况)。对于dest <= src而言,情况更为复杂。但是,正如已经提到的其他答案一样,从背后进行复制会带来成功。例如:

dest > src