【问题标题】:Does comparing two pointers do more than to just check their adresses?比较两个指针不仅仅是检查它们的地址吗?
【发布时间】:2021-12-10 21:43:12
【问题描述】:

我正在重写 libc 的一部分,更具体地说 memmove 作为一个练习,在我自己无法做到之后,我查找了它的源代码并找到了这个(以及它的变体):

void    *memmove(void *dest, const void *src, size_t n)
{
  char *d = dest;
  const char *s = src;
  if (d < s)
    while (n--)
      *d++ = *s++;
  else
    {
      char *lasts = s + (n-1);
      char *lastd = d + (n-1);
      while (n--)
        *lastd-- = *lasts--;
    }
  return (dest);
}

我的问题涉及第 5 行 if (d &lt; s) 对我来说,这只有在 ds 包含 destsrc 的大小时才有意义,据我所知这是不正确的。那么除了检查地址的位置之外,这种比较还有其他作用吗?还是我对指针的理解完全有缺陷?

这段代码有什么用途吗?如果我完全错了,请提前抱歉,指针可能会很混乱。

【问题讨论】:

  • 指针不包含它所指向的对象的长度。进行此检查的原因是因为参数可能重叠。为了确保来自src 的数据在被复制之前不会被覆盖,算法会检查dest 是在src 的左侧还是右侧。如果dest 在左侧,则从左侧开始复制,然后沿着srcdest 向右移动。如果是向右,则从右侧开始复制,向左移动。
  • @LouisCloete 是堆中的地址还是栈中的地址?
  • @MiguelP 没关系。
  • 请注意,库例程的实现使用了 C 标准未在普通用户代码中定义的比较。只有当ds 指向同一个数组时,C 标准才定义d &lt; s,包括最后一个数组元素之后的名义位置。但是,即使ds 指向完全不同的对象,标准库函数memmove 也必须能够工作。这使得使用严格符合 C 代码的 memmove 无法实现;它必须依赖编译器来定义 d &lt; s,即使 C 标准没有。
  • @EricPostpischil (uintptr_t)d &lt; (uintptr_t)s 在大多数系统上给出了合理的结果。

标签: c performance pointers c-strings memmove


【解决方案1】:

函数 memmove 假设源数组和目标数组是重叠的。也就是说,它们是指向同一数组不同部分的指针。 所以例如这个 if 语句

if (d < s)

检查指针d指向的元素是否在数组中指针s指向的元素之前。

考虑一个例子。假设声明了一个数组

char s[] = "Hello World!";

即数组包含字符串"Hello World!"。并且您想更改数组以使其包含字符串“World!”。您需要移动源字符串中包含子字符串“World!”的部分。到数组的开头。

如果你会写

strcpy( s, s + 6 );

然后这样的调用会调用未定义的行为,因为函数 strcpy 不允许使用重叠子数组。

使用 memmove 你可以写

memmove( s, s + 6, 7 );

在这种情况下,相对于您提供的函数定义,指针 d 具有原始数组 s 的值,而指针 s 具有等于表达式 s + 6 的值。

由于 d 的值小于指针 s 的值,所以这部分函数得到控制

if (d < s)
  while (n--)
    *d++ = *s++;

注意按照C标准(6.5.8关系运算符)

5 比较两个指针时,结果取决于相对 指向的对象的地址空间中的位置。如果两个 指向对象类型的指针都指向同一个对象,或者都指向 超过同一数组对象的最后一个元素,它们比较 平等的。如果指向的对象是同一聚合的成员 对象,指向稍后声明的结构成员的指针比较 大于指向结构中早先声明的成员的指针, 和 指向具有较大下标值的数组元素的指针比较 大于指向同一数组元素的指针 下标值。 指向同一联合对象成员的所有指针 比较相等。 如果表达式 P 指向一个元素 数组对象和表达式 Q 指向的最后一个元素 同一个数组对象,指针表达式 Q+1 比较大于 P。 在所有其他情况下,行为是未定义的。

从引用中可以看出,传递给函数 memmove 的两个指针应该指向同一个数组的元素,以保证在 if 语句中比较指针的定义行为

if (d < s)

否则行为未定义。

【讨论】:

  • 所以不是为了堆/栈控制,而是允许用户对函数参数进行指针运算? NUL 终止和字符串的其余部分呢?
  • @MiguelP 在这个例子中 memmove( s, s + 6, 7 );由于使用了等于 7 的值,所以会抵消 nul 终止字符。
  • Re“函数 memmove 假定源数组和目标数组是重叠的。”:不,它假定它们可能重叠;也就是说,无论源和目标是否重叠,它都有效。
  • @EricPostpischil 当指针指向同一个数组的元素(如果它们不是结构的成员)时,使用运算符
  • 关于“传递给函数 memmove 的两个指针应指向同一个数组的元素”:这对于 memmove 的标准库实现不正确。标准库例程的实现不受严格遵守 C 的约束;它们可以而且有时必须依赖于实现定义的行为,而不是 C 标准。在 C 2018 7.24.2.2 中,标准指定 memmove 在任意两个对象之间进行复制。为了符合这个规范,无论对象是同一对象的子对象还是完全不相关,它都必须起作用。
【解决方案2】:

除了检查地址的位置之外,这种比较还有什么作用吗?

没有。它检查d 的地址是否低于s。如果是,它选择第一个策略,从内存区域的开头向前复制 - 否则它从内存区域的末尾向后复制

这是为了避免在[d,d+n)[s,s+n) 重叠的情况下弄乱结果。

例子:

     +---+---+---+
src  | x | y | z |
     +---+---+---+---+
dest     |   |   |   |
         +---+---+---+
     ^               ^
     |               |
low mem addr    high mem addr

这里必须选择第二种策略。考虑一下如果选择第一个策略会发生什么:

dest[0] = src[0]; // which is x
dest[1] = src[1]; // which is ... x, because the above op overwrote the y

【讨论】:

    猜你喜欢
    • 2013-12-04
    • 1970-01-01
    • 2014-03-22
    • 1970-01-01
    • 2013-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多