如何修复strcpy以便检测重叠的字符串
在一次采访中,我被要求编写strcpy的实现,然后修复它以便正确处理重叠的字符串。 我的实现在下面,它是非常天真的..我如何修复它,以便a)它检测重叠的字符串和b)检测后..我们如何处理重叠并继续?
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)) { 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)) { 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; }
没有可移植的方法来检测这一点。 您必须进行指针比较,并且这些仅在同一对象中定义。 即如果两个字符串不重叠并且实际上是不同的对象,则指针比较会给出未定义的行为。
我会让标准库通过使用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 tring 0 _ _ _ _ _ _ _ ^ ^ | | ba
注意, b + strlen(b)
导致指向终止\ 0的指针。 开始一个,否则你需要额外处理边缘情况。 在那里设置指针是有效的,你不能取消引用它们。
src = b + strlen(b) + 1; dst = a + strlen(b) + 1; S tring 0 _ _ _ _ _ _ _ ^ ^ ^ ^ | | | | ba src dst
现在复制循环也复制了\ 0。
while (src > b) { src--; dst--; *dst = *src; }
第一步是这样的:
src--; dst--; S tring 0 _ _ _ _ _ _ _ ^ ^ ^ ^ | | | | ba src dst *dst = *src; S tring 0 _ _ _ 0 _ _ _ ^ ^ ^ ^ | | | | ba src dst
依此类推,直到src
结束等于b
:
S tri S tring 0 _ _ _ ^ ^ | | ba src dst
如果你想要它更加hackish,你可以进一步压缩它,但我不建议这样:
while (src > b) *(--dst) = *(--src);
注意:这里, b
是源字符串的地址, a
是目标的地址。
使用a > b
您不一定会有重叠。 如果
(a <= b && a+strlen(a) >= b) || (b <= a && b+strlen(b) >= a)
然后你有重叠。
但是,除了为了采访而检测重叠之外, a > b
应该对strcpy
做好。 这个想法是这样的:
如果b
进一步放在内存中( b > a
),那么你通常可以将b
复制到a
。 b
将被覆盖,但你已经超过了那一部分。
如果a
进一步放在内存中( a > b
),则意味着可能通过写入a
的第一个位置,您已经覆盖了b
具有更高索引的位置。 在这种情况下,您应该以相反的方向复制。 因此,不应该从索引0
复制到strlen(b)-1
,而应该从strlen(b)-1
复制到0
。
如果您对此有何帮助感到困惑,请在纸上绘制两个重叠的数组,并尝试从数组的开头复制一次,然后从结尾复制一次。 在a > b
和a < b
情况下,尝试使用重叠数组。
注意,如果a == b
,你不需要实际复制任何东西,你可以返回。
编辑:我不确定,但阅读其他解决方案,似乎这个答案可能不完全可移植。 要小心。
如果您希望字符串重叠,则可以使用memmove()。
char* my_strcpy(char *a, char *b) { memmove(a, b, strlen(b) + 1); return a; }
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
的实现 ,它就像我说的那样。
if (a>= b && a <= b+strlen(b))) || (b+strlen(b) >= a && b+strlen(b) <= a + strlen(b))
(*)你应该缓存strlen(b)来提高性能
它能做什么:
检查a+len
[+额外len字节的地址]是否在字符串内,或者[地址]是否在字符串内,这些是字符串重叠的唯一可能性。
如果这两个字符串重叠,那么,在复制时,您将遍历原始的a
或b
指针。
假设strcpy(a,b)大致意味着< - b,即第一个参数是副本的目的地,那么你只检查复制指针是否到达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; }
该算法只是停止复制。 也许你想做其他的事情,比如标记错误条件,或者将字符串标记的末尾添加到前一个位置(虽然默默地失败(因为算法目前做的)并不是最好的选择)。
希望这可以帮助。
我在最近的一次采访中被问到这个问题。 我们不必’检测’重叠。 我们可以用重叠地址处理的方式编写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--; } }
编辑:这只适用于 b,从头开始复制。
即使不使用关系指针比较, memmove
或等效,也可以编写strcpy
的版本,它将在非重叠的情况下作为strlen
和memcpy
执行,并在重叠的情况下作为自上而下的副本执行。 关键是利用如下事实:如果读取目标的第一个字节然后替换为零,则在源上调用strlen
并添加到源指针,返回的值将产生一个合法的指针,该指针将等于“麻烦重叠”案件中的目的地。 如果源和目标是不同的对象,则可以安全地计算“source plus strlen”指针并观察其与目的地不相等。
如果将字符串长度添加到源指针产生目标指针,则使用较早读取的值替换零字节并在目标上调用strlen将允许代码确定源字符串和目标字符串的结束地址。 此外,源串的长度将指示指针之间的距离。 如果此值很大(可能大于16左右),则代码可以有效地将“移动”操作细分为自上而下的memcpy操作序列。 否则,可以使用自上而下的单字节复制操作循环复制字符串,或者使用“memcpy to source to buffer”/“memcpy buffer to destination”操作[如果是大字节的每字节成本]复制该字符串不到个别字符复制循环的一半,使用~256字节的缓冲区可能是一个有用的优化]。