在一次采访中,我被要求编写strcpy
的实现,然后修复它以便正确处理重叠的字符串。我的实现如下,非常天真。我该如何修复它:
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;
}
}
答案 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
复制到a
。 b
的部分内容将被覆盖,但您已经超过了该部分。
如果将a
放在内存中a > b
),则表示可能,写在a
的第一个位置,您已经覆盖b
中具有更高索引的位置。在这种情况下,您应该以相反的方向复制。因此,您应该从0
复制到strlen(b)-1
,而不是从索引strlen(b)-1
复制到0
。
如果您对此有何帮助感到困惑,请在纸上绘制两个重叠的数组,并尝试从数组的开头复制一次,然后从结尾复制一次。在a > b
和a < 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)
如果这两个字符串重叠,那么,在复制时,您将覆盖原始的a
或b
指针。
假设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
的版本进行编码,该版本将作为strlen
和memcpy
执行不重叠的情况,以及重叠情况下的自上而下的副本。关键是利用如下事实:如果读取目标的第一个字节然后替换为零,则在源上调用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