【问题标题】:how is distance between two addresses computed?如何计算两个地址之间的距离?
【发布时间】:2018-04-09 19:25:26
【问题描述】:

我想计算两个地址之间的字节数。

uint32_t length = &b - &a;

当a和b为uint32_t时,长度为1。

uint32_t a, b;
uint32_t length = &b - &a;  // length is one

当a和b为uint8_t时,长度为4。

uint8_t a, b;
uint32_t length = &b - &a;  // length is four

因此,计算是 a 和 b 之间的 uint32_t 或 uint8_t 的数量,而不是我错误预期的地址之间的数学差异。

我的问题:C 语言的哪一部分涉及地址计算?有人可以参考规范中讨论该主题的位置吗?

【问题讨论】:

  • 技术上,不指向同一个对象的指针之间的指针运算是未定义的行为
  • 指针都有一个类型。他们指向什么样的东西?两个指针之间的区别在于它们之间的 事物 的数量,而不是字节数。
  • 很好奇,为什么代码使用类型uint32_t 表示uint32_t length = &b - &a; 而不是intlong longptrdiff_t
  • 在某些系统上,不同的对象可能放在different memory segments 中,然后可能没有距离。

标签: c pointers memory byte sizeof


【解决方案1】:

C standard 的 6.5.6 节介绍了指针减法:

3 对于减法,应满足以下条件之一:

  • 两个操作数都有算术类型;
  • 两个操作数都是指向兼容完整对象类型的合格或不合格版本的指针;
  • 左操作数是指向完整对象类型的指针,右操作数是整数类型。

...

9 当两个指针相减时,两个指针都应该指向同一个数组对象的元素,或者指向数组最后一个元素之后的元素 目的;结果是两者下标的差 数组元素。 结果的大小由实现定义,并且 它的类型(有符号整数类型)是在 标题。如果结果不能在对象中表示 该类型,行为未定义。换句话说,如果 表达式 P 和 Q 分别指向第 i 个和第 j 个元素 对于数组对象,表达式 (P)-(Q) 具有提供的值 i-j 该值适合 ptrdiff_t 类型的对象。此外,如果 表达式 P 指向数组对象的一个​​元素或一个 越过数组对象的最后一个元素,表达式 Q 指向 到同一数组对象的最后一个元素,表达式 ((Q)+1)-(P) 与 ((Q)-(P))+1 和 -((P)-((Q)+1)) 具有相同的值, 如果表达式 P 指向最后一个,则值为 0 数组对象的元素,即使表达式 (Q)+1 没有 指向数组对象的一个​​元素。

所以区别在于两者之间的元素数,而不是字节数。

请注意,这只允许在同一数组的两个元素之间减去指针。所以这是合法的:

uint32_t a[5];
uint32_t len = &a[1] - &a[0];

但这不是:

uint32_t a, b
uint32_t len = &b - &a;

【讨论】:

    【解决方案2】:

    如果指针具有不同的类型或不指向同一个内存块(即表或以其他方式分配),则在符合标准的 C 指针算术中是不允许的。否则就是UB

    但是如果变量位于相同的连续地址空间中 - 例如在 ARM uC 中,如果指针具有相同的类型或者您将它们转换为相同的类型,则该算法的结果将被定义。

    这不是符合 C 标准的代码

    #include <stdio.h>
    #include <stdint.h>
    
    
    uint64_t c;
    uint64_t d;
    uint16_t e;
    uint8_t f;
    
    
    int main(void)
    {   uint32_t a,b;
        printf("%lld\n", (long long)((uint8_t *)&b - (uint8_t *)&a));
        printf("%lld\n", (long long)((uint8_t *)&c - (uint8_t *)&a));
        printf("%lld\n", (long long)((uint8_t *)&d - (uint8_t *)&c));
        printf("%lld\n", (long long)((uint8_t *)&e - (uint8_t *)&d));
        printf("%lld\n", (long long)((uint8_t *)&f - (uint8_t *)&c));
    }
    

    将打印的内容是 100% 直至实施。一些结果可能有另一种感觉。

    这种算法用于嵌入式开发,例如通过在链接描述文件中定义符号(例如 bss 的开头和 bss 的结尾),然后这些符号(实际上是它们的地址)用于执行诸如归零之类的操作bss或初始化数据段

    你可以在 Linux 机器上试试: https://ideone.com/dm0R5M

    【讨论】:

      【解决方案3】:

      我想计算两个地址之间的字节数。

      如果地址在同一个数组中,代码可以减去指针来计算差异中元素的数量。然后乘以类型的大小,就得出“字节”的数量。

      ptrdiff_t diff = &a[some_index] - &a[some__other_index];
      diff *= sizeof a[0];
      printf("Diff %td\n", diff);
      

      如果不知道 2 个对象的地址在同一个数组中,代码可以小心地减去,但根据内存模型,差异可能代表也可能不代表“字节”差异。 IAC,下面避免了未定义的行为

      #include <inttypes.h>
      #include <stdio.h>
      
      void *va = &a;
      void *vb = &b;
      // optional types
      uintptr_t ua = (uintptr_t)va;
      uintptr_t ub = (uintptr_t)vb;
      uintptr_t diff = ua > ub ? ua - ub : ub - ua;
      printf("Maybe byte difference of %" PRIuPTR "\n", diff); 
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-01-21
        • 1970-01-01
        • 2011-03-18
        • 1970-01-01
        • 1970-01-01
        • 2014-10-21
        • 1970-01-01
        相关资源
        最近更新 更多