如何修复 strcpy 以便检测重叠字符串
Posted
技术标签:
【中文标题】如何修复 strcpy 以便检测重叠字符串【英文标题】:How to fix strcpy so that it detects overlapping strings 【发布时间】:2011-11-17 15:52:27 【问题描述】:在一次采访中,我被要求编写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;
【问题讨论】:
a > b
应该如何“检测重叠”?它只是测试两个地址。
您可以进行两次复制:首先复制到本地缓冲区,没有重叠的机会,然后从本地缓冲区复制到目的地。
@pmg:你可以,但是必须允许my_strcpy
使 ENOMEM 失败。
@Steve:对——“天下没有免费的午餐”;尽管一开始就做两份副本与 免费午餐 相去甚远 :-)
关于你的编辑,作为面试官,我的下一个问题是:你为什么不依赖 memmove,而是用一个单行来代替一个不可维护的指针处理混乱?
【参考方案1】:
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
中的一个implementation,和我说的很像。
【讨论】:
【参考方案2】:没有可移植的方法来检测这一点。您必须进行指针比较,并且这些仅在同一个对象中定义。 IE。如果两个字符串不重叠并且实际上是不同的对象,那么指针比较会给您未定义的行为。
我会让标准库处理这个问题,使用memmove(a, b, strlen(b) + 1)
。
编辑:
正如 Steve Jessop 在 cmets 中指出的那样,在这种情况下,实际上有一种可移植但缓慢的方法来检测重叠。比较 b 中的每个地址与 a 的第一个和最后一个地址是否相等。与==
的相等比较总是明确定义的。
所以你有这样的东西:
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
如果你想要它更hackish,你可以进一步压缩它,但我不建议这样做:
while (src > b)
*(--dst) = *(--src);
【讨论】:
不能便携检测重叠是不正确的。有一种令人震惊的低效方法。这是给memmove的,但我相信它可以适应strcpy:***.com/questions/4023320/… "你必须做指针比较,这些只在同一个对象中定义。" -如何检查两个指针是否指向同一个对象(本例中为数组) @Steve Jessop:正确,但在这种情况下它不能被便携检测,因为你只有两个指针,但没有它们指向的数组边界(起始地址和大小)。 @user639309:它必须作为已知的前提条件给出。否则,如果它们不是,则您无法在不调用未定义行为的情况下检查它,除非史蒂夫指出数组边界是已知的。然后您可以检查一个数组中的每个可能的地址是否等于另一个指针。 OTOH,如果您知道 both 数组的边界,您可以简单地检查它们是否相等。带有 == 的指针的相等性检查始终是明确定义的,相同对象规则仅适用于小于或大于检查。 @Secure:但正如你自己所说,strlen(b)+1
给了你这个大小。如果调用者首先做了一些无效的事情,它只会出错,例如如果a
没有指向足够大的缓冲区,但这不是我们的错。【参考方案3】:
如果您希望字符串重叠,您可以使用 memmove()。
char* my_strcpy(char *a, char *b)
memmove(a, b, strlen(b) + 1);
return a;
【讨论】:
考虑到一个字符 == 一个字节。我将 strlen(b) + 1 更改为 ( strlen(b) + 1 ) * sizeof( char ) sizeof(char) 总是正好是一个字节。 是的,但是 memmove 需要字节,而不是字符,即使它们偶然具有相同的大小。无论如何,我只是说“我愿意”。 @Baltasarq "memmove 需要字节,而不是字符" 具有误导性。memmove()
期望字符大小,在 C 中,“字节”和字符具有相同的大小。 "memmove函数将s2指向的对象中的n个字符复制到s1指向的对象中。C11 "7.24.2.2"【参考方案4】:
if (a>= b && a <= b+strlen(b))) || (b+strlen(b) >= a && b+strlen(b) <= a + strlen(b))
(*) 你应该缓存 strlen(b) 以提高性能
它的作用:
检查a+len
[address of a + extra len bytes] 是否在字符串内,或者a
[address of a] 在字符串内,这些是字符串重叠的唯一可能性。
【讨论】:
【参考方案5】:我在最近的一次采访中被问到这个问题。我们不必“检测”重叠。我们可以写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--;
编辑:这仅在 a b,从头开始复制。
【讨论】:
如果字符串重叠,问题仍然存在。与memcpy
一样,您应该从头或尾复制,具体取决于要复制的目标地址是比源地址低还是高。
1) 代码无法编译。 2) 建议重新设计答案/代码以使用src dest
,而不是a b
。 3) strlen()
返回类型size_t
,但随后size_t i
导致while(i>=0)
测试出现问题,这始终是正确的。【参考方案6】:
注意:这里,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
的第一个位置,你已经覆盖了@987654335中的一个位置@ 具有更高的索引。在这种情况下,您应该向相反的方向复制。所以不要从索引0
复制到strlen(b)-1
,你应该从strlen(b)-1
复制到0
。
如果您对这有什么帮助感到困惑,请在纸上绘制两个重叠的数组,并尝试从数组的开头复制一次,从结尾复制一次。在 a > b
和 a < b
的情况下尝试使用重叠数组。
注意,如果a == b
,你不需要实际复制任何东西,你可以直接返回。
编辑:我不确定,但阅读其他解决方案,似乎这个答案可能不是完全可移植的。小心那个。
【讨论】:
如果a==b
,您甚至可以直接返回:-) strcpy
采用指向非易失性的指针,因此不需要实际接触内存。也就是说,不值得添加代码来优化这种荒谬的情况。
@chux,您是否考虑了终止 NUL?【参考方案7】:
如果这两个字符串重叠,那么,在复制时,您会遇到原始的 a
或 b
指针。
假设strcpy(a, b)大致意思是ab的位置。
你只需要保存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;
这个算法只是停止复制。也许您想做其他事情,例如标记错误条件,或者在前一个位置添加一个字符串结尾标记(尽管静默失败(就像算法目前所做的那样)不是最好的选择)。
希望这会有所帮助。
【讨论】:
【参考方案8】:即使不使用关系指针比较、memmove
或等效项,也可以编写 strcpy
的版本,在不重叠的情况下将作为 strlen
和 memcpy
执行,并且作为重叠情况下的自上而下的副本。关键是要利用这样一个事实,即如果读取目标的第一个字节然后用零替换,则在源上调用strlen
并将返回的值添加到源指针将产生一个合法的指针,它将等于在“麻烦的重叠”情况下目的地的开始。如果源和目标是不同的对象,则可以安全地计算“源加 strlen”指针并观察到不等于目标。
如果将字符串长度添加到源指针产生目标指针,则将零字节替换为较早读取的值并在目标上调用 strlen 将允许代码确定源和目标字符串的结束地址.此外,源字符串的长度将指示指针之间的距离。如果这个值很大(可能大于 16 左右),代码可以有效地将“移动”操作细分为自上而下的 memcpy 操作序列。否则,可以使用自上而下的单字节复制操作循环复制字符串,或者使用“memcpy 到源到缓冲区”/“memcpy 缓冲区到目标”操作的序列[如果大型 memcpy 的每字节成本小于单个字符复制循环的一半,使用约 256 字节的缓冲区可能是一个有用的优化]。
【讨论】:
【参考方案9】:这个 SO 条目已经很老了,但我目前正在处理一段旧代码,它使用 strcpy()
复制重叠的字符串。日志输出中缺少字符。我决定使用以下紧凑的解决方案,将char
复制到char
。
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;
编辑:
正如@Gerhardh 指出的那样,上面的代码只有在dest <= src
时才有效(我只需要解决这个问题)。对于dest > src
的情况,情况更复杂。但是,正如其他答案已经提到的那样,从后面复制会导致成功。例如:
if (dest <= src)
/* do the above */
else
int i = (int)strlen(src);
while (i >= 0)
dst[i] = src[i];
i--;
【讨论】:
这如何解决重叠部分?假设strlen(src) == 20
和dest=src+5
@Gerhardh 我只需要解决strcpy(posPtr, posPtr+2);
。反之则一团糟。
检测和处理混乱将是这种功能的重点。 ;)以上是关于如何修复 strcpy 以便检测重叠字符串的主要内容,如果未能解决你的问题,请参考以下文章