这是检查内存中是否有两个c字符串重叠的正确且可移植的方法吗?

这是检查内存中是否有两个c字符串重叠的正确且可移植的方法吗?,c,C,可能不是最有效的方法,但它是否正确且可移植 int are_overlapping(const char *a, const char *b) { return (a + strlen(a) == b + strlen(b)); } 澄清一下:我要寻找的是内存中的重叠,而不是实际内容中的重叠。例如: const char a[] = "string"; const char b[] = "another string"; are_overlapping(a, b); // should r

可能不是最有效的方法,但它是否正确且可移植

int are_overlapping(const char *a, const char *b) {
  return (a + strlen(a) == b + strlen(b));
}
澄清一下:我要寻找的是内存中的重叠,而不是实际内容中的重叠。例如:

const char a[] = "string";
const char b[] = "another string";
are_overlapping(a, b); // should return 0
are_overlapping(a, a + 3); // should return 1

是的,你的代码是正确的。如果两个字符串在示例位置结束,根据定义,它们重叠-它们共享同一个空终止符。要么两个字符串相同,要么一个是另一个的子字符串

程序的所有内容都是完全定义良好的行为,因此假设编译器符合标准,它应该是完全可移植的

标准中的相关位来自6.5.9等式运算符(强调):

两个指针比较相等当且仅当都是空指针,都是指向同一对象的指针(包括指向对象的指针和位于其开头的子对象)或函数,都是指向同一数组对象最后一个元素的指针,或者一个指针指向一个数组对象的末尾,另一个指针指向另一个数组对象的开头,该数组对象恰好紧跟在地址空间中的第一个数组对象之后


考虑到zdan对我上一篇文章的评论(可能很快就会被删除),我得出结论,检查端点就足够了

如果有任何重叠,空终止符将使两个字符串不明显。让我们看看一些可能性

如果你从

a 0x10000000 "Hello" and somehow add
b 0x10000004 "World",
您将有一个单词:HellWorld,因为W将覆盖\0。它们将在同一个端点结束

如果你以某种方式写到同一个起点:

a 0x10000000 "Hello" and
b 0x10000000 "Jupiter"
你会有木星这个词,并且有相同的端点

是否存在可以具有相同端点而不具有重叠的情况?有点

a = 0x1000000 "Four" and
b = 0x1000004 "".
这也会产生重叠

我想不出有哪一次会有重叠,没有匹配的端点——假设您正在向内存中写入以null结尾的字符串


因此,简短的回答是:是的,您的检查就足够了。

此解决方案仍然具有相同的最坏情况性能,但针对命中率进行了优化——您不必同时解析两个字符串

char * temp_a = a;
char * temp_b = b;

while (*temp_a != '\0') {

    if (temp_a++ == b) 
        return 1;

}

// check for b being an empty string
if (temp_a == b) return 1;

/* but if b was larger, we aren't done, so you have to try from b now */
while (*temp_b != '\0') {
    if (temp_b++ == a)
        return 1;
}

/* don't need the a==b check again here

return 0;
显然,只有指针相等(而不是不相等)在C中是可移植的,所以下面的解决方案是不可移植的——下面的所有内容都是我知道之前的内容

您的解决方案是有效的,但为什么要在第二个字符串上计算strlen?您知道一个字符串的起点和终点,只需查看另一个字符串是否在它们之间(包括两者)。保存通过第二个字符串的过程--O(M+N)到O(M)

char*lower\u addr\u string=ab?a:b
长度=strlen(较低的地址字符串)
返回较高的地址字符串>=较低的地址字符串和较高的地址字符串b?a:b
while(*lower\u addr\u string!='\0'){
if(较低的地址字符串==较高的地址字符串)
返回1;
++下地址字符串;
}
/*检查最后一个字符*/
if(较低的地址字符串==较高的地址字符串)
返回1;
返回0;

是的,您的检查是正确的,但它肯定不是最有效的(如果“效率”是指计算效率)。实现中显而易见的直观效率低下是基于这样一个事实:当字符串实际重叠时,
strlen
调用将在它们的公共部分上迭代两次

为了形式上的效率,可以使用稍微不同的方法

int are_overlapping(const char *a, const char *b) 
{
  if (a > b) /* or `(uintptr_t) a > (uintptr_t) b`, see note below! */
  {
    const char *t = a; 
    a = b; 
    b = t;
  }

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

  return a == b;
}
关于这个版本的一个重要注意事项是,它执行两个指针的关系比较,这两个指针不能保证指向同一个数组,这在形式上导致了未定义的行为。它将在具有扁平内存模型的系统上实际工作,但可能会招致学究式代码审阅者的批评。为了正式解决这个问题,可以在执行关系比较之前将指针转换为
uintptr\t
。这样,在大多数(如果不是全部)具有平面内存模型的传统实现中,未定义的行为将转换为具有适当语义的实现定义的行为

这种方法不存在“重复计数”问题:它只分析位于内存中“较早”的字符串的非重叠部分。当然,在实践中,这种方法的好处可能是不存在的。这将取决于
strlen
实现的质量和实际输入的属性

例如,在这种情况下

const char *str = "Very very very long string, say 64K characters long......";

are_overlapped(str, str + 1);
我的版本将比你的版本更快地检测到重叠。我的版本将在周期的1次迭代中完成,而您的版本将花费2*64K次迭代(假设是
strlen
的简单实现)

如果您决定深入到有问题的指针比较领域,上述想法也可以重新实现为

int are_overlapping(const char *a, const char *b) 
{
  if (a > b)
  {
    const char *t = a; 
    a = b; 
    b = t;
  }

  return b <= a + strlen(a);
}
int重叠(常量字符*a,常量字符*b)
{
如果(a>b)
{
常数char*t=a;
a=b;
b=t;
}

返回b它可能与您的用例无关,因为您的问题是关于C字符串的,但是如果数据在字符串中嵌入了NUL字节,那么代码将不起作用

char a[] = "abcd\0ABCD";
char *b = a + 5;
除此之外,您的解决方案是直截了当且正确的。因为您仅使用
=
进行指针比较,并且符合标准(来自C116.5.9/6)

两个指针比较相等当且仅当两个指针都是空指针时,两个指针都是指向同一对象的指针(包括指向对象的指针和位于其开头的子对象)或者函数,两者都是指向同一数组对象的最后一个元素的指针,或者一个是指向一个数组对象末尾的指针,另一个是指向另一个数组对象的开头的指针,该数组对象恰好紧跟在地址空间中的第一个数组对象之后

int are_overlapping(const char *a, const char *b) 
{
  if (a > b)
  {
    const char *t = a; 
    a = b; 
    b = t;
  }

  return b <= a + strlen(a);
}
char a[] = "abcd\0ABCD";
char *b = a + 5;