这是检查2个Cstring是否在内存中重叠的正确和便携的方法吗?

可能不是最有效的方式,但它是否正确和便携?

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 

是的,你的代码是正确的。 如果两个string在样本位置结束,则它们按照定义重叠 – 它们共享相同的空终止符。 两个string都是相同的,或者一个是另一个的子string。

关于你的程序的一切都是完美定义的行为,所以假设符合标准的编译器,它应该是完全可移植的。

标准中的相关位是从6.5.9平等运算符 (重点是我的):

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

考虑到zdan对我以前的post(可能很快就会被删除)的评论,我得出结论,检查端点是足够的。

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

如果你开始

 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 "". 

这也会产生重叠。

我想不出任何时候你会有重叠的地方,你将不会有匹配的端点 – 假设你正在写入空string到内存中

所以,简短的回答:是的,你的支票就足够了。

这可能与您的用例无关,因为您的问题特别是关于Cstring,但是在数据embeddedNUL字节的情况下,代码将不起作用。

 char a[] = "abcd\0ABCD"; char *b = a + 5; 

除此之外,你的解决scheme是直接和正确的。 它工作,因为你只使用==作为指针比较,并根据标准(从C11 6.5.9 / 6)

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

但是,关系运算符更为严格(从C11 6.5.8 / 5开始):

当比较两个指针时,结果取决于指向对象的地址空间中的相对位置。 如果指向对象types的两个指针都指向相同的对象,或者两者都指向同一个数组对象的最后一个元素,则它们相等。 如果指向的对象是同一个聚合对象的成员,则稍后声明的结构成员的指针比结构中较早声明的成员的指针要多,指向具有较大下标值的数组元素的指针比指向同一数组元素的指针具有较低的下标值。 所有指向同一联合对象的成员的指针相等。 如果expression式P指向数组对象的元素,而expression式Q指向同一数组对象的最后一个元素,则指针expression式Q + 1的比较大于P.在所有其他情况下,行为是不确定的。

最后一句是踢球。

一些人认为,你的代码可能会计算两次重叠的长度,并试图采取预防措施来避免这种情况。 但是,减less计算的效率会在每次迭代中额外进行指针比较,或者涉及到未定义或实现定义的行为。 假设你想要一个便携式和兼容的解决scheme,实际的平均增益可能是零,不值得付出努力。

这个解决scheme仍然是最差情况下的性能,但是对命中进行了优化 – 您不必分析两个string。

 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中只有指针相等(不是不等式)是可移植的,所以下面的解决scheme是不可移植的 – 下面的所有东西都是在我知道之前。

你的解决scheme是有效的,但为什么计算第二个stringstrlen? 你知道一个string的开始和结束点,看看是否在他们之间(包括)。 (M + N)到O(M)

 char * lower_addr_string = a < b ? a : b char * higher_addr_string = a > b ? a : b length = strlen(lower_addr_string) return higher_addr_string >= lower_addr_string && higher_addr_string <= lower_addr_string + length; 

或者,自己做stringparsing..

 char * lower_addr_string = a < b ? a : b char * higher_addr_string = a > b ? a : b while(*lower_addr_string != '\0') { if (lower_addr_string == higher_addr_string) return 1; ++lower_addr_string; } /* check the last character */ if (lower_addr_string == higher_addr_string) return 1; return 0; 

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

为了forms上的效率,可以采用稍微不同的方法

 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 。 这样,未定义的行为被转换为具有适当语义的实现定义的行为,用于大多数(如果不是全部的话)具有平坦内存模型的传统实现中的目的。

这种方法不存在“重复计算”问题:它只分析内存中“较早”位置的string的非重叠部分。 当然,实际上这种方法的好处可能是不存在的。 这将取决于你的strlen实现的质量和实际input的属性。

例如,在这种情况下

 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); } 

此实现不会在每次迭代中执行额外的指针比较。 我们付出的代价是,它总是迭代到一个string的末尾,而不是提前终止。 然而它比你的实现更有效率,因为它只调用strlen一次。