【问题标题】:C pointers, playing around with castingC 指针,玩转转换
【发布时间】:2021-06-06 10:23:01
【问题描述】:

我在我正在处理的项目中偶然发现了一些代码,我想确保正确理解它。 所以这里是:

uint16_t* tmp;

tmp = (uint16_t*) ((uint8_t*)getVariableAddress(variable) + offset);

tmp = (uint16_t*)((uint8_t*)tmp + otherOffset);
Set_Register((unsigned long) tmp[0]);
Set_OtherRegister((unsigned long) tmp[2]);

起初我在所有转换之间有点迷失,但我看到它的方式是 uint8_t* 被用于逐字节移动并将偏移值添加到我们放置在 tmp 中的基地址,这是第一部分这让我很困扰。 第二部分是在 uint16_t* 上使用 [] ,对此我完全不确定结果,有人愿意详细解释吗?

谢谢

【问题讨论】:

  • 如果偏移量以字节为单位但类型为 16 位,则强制转换可确保指针算术正确。如果类型是 16 位,则 pointer + 12 添加到指针值。至于tmp[2],这与*(tmp + 2) 相同,并且转换应用于数据值,而不是指针类型。
  • 值得注意的是,除非实际存储在该地址的内容确实是 uint16_t,否则此代码会调用未定义的行为错误。 getVariableAddress 做了什么,从它返回的指针指向哪里?这段代码很可能写错了。
  • 项目中该代码的编写者可能希望了解将 uint8_t 指针转换为 uint16_t 指针违反严格的别名规则。
  • 代码可能从根本上被破坏了。正如@Pedro 所指出的,这违反了strict aliasing,因此是未定义的行为。由于alignment restrictions,它也可能是未定义的行为。不,it doesn't "work"。它只是没有失败
  • @AndrewHenle:作为“符合语言扩展”的一种形式,实现可以定义比标准规定的更多动作的行为。何时以这种方式扩展语言的问题是标准管辖范围之外的实施质量问题。依赖于此类扩展的代码不会严格符合,也不会 100% 可移植,但这并不意味着它不符合或损坏。在以所需方式扩展语言的实现上,它将 100% 可靠地工作。

标签: c pointers casting


【解决方案1】:

我认为如果我们稍微重写一下代码会更容易看出会发生什么:

// We keep this as a `uint8_t *` so we can add offsets correctly.
uint8_t *base_address = (uint8_t *)getVariableAddress(variable);

// Add the offsets to the base address.
uint8_t *offsetted_address = base_address + offset + otherOffset;

// We want it as a uint16_t.
uint16_t *as_u16 = (uint16_t *)offsetted_address;

// We want to write the first register with the first `uint16_t` at `offsetted_address`, but `Set_Register` takes the value as `unsigned long`.
unsigned long first_u16 = as_u16[0];
Set_Register(first_u16);

// We want to write the other register with the third `uint16_t` at `offsetted_address`, but `Set_OtherRegister` takes the value as `unsigned long`.
unsigned long third_u16 = as_u16[2];
Set_OtherRegister(third_u16 );

我们知道我们感兴趣的值相对于getVariableAddress 返回的地址有一个偏移量。为了正确计算该地址,我们将地址转换为uint8_t。如果我们保留为uint16_t,我们的算术将是错误的。考虑一下:

uint8_t *p = (uint8_t *)0x100;
printf("%p\n", p + 1); // prints 0x101

uint16_t *q = (uint16_t *)0x200;
printf("%p\n", q + 1); // prints 0x202

然后我们想从刚刚计算的地址中读取第一个和第三个 16 位无符号整数,因此我们将其转换为 uint16_t * 并获取第一个 ([0]) 和第三个 ([2]) 整数.

【讨论】:

    【解决方案2】:

    当您使用以下内容时:

    int var[5];
    var[3] = 1;
    

    当声明 var 时,它会在内存中连续分配 5 个整数,var 只获得指向这 5 个内存插槽中第一个的指针,本质上是一个 int*

    然后,当您使用var[3] 访问它时,您是在告诉它以 3 倍 sizeof(int) 的偏移量访问第一个内存地址。

    在您的示例中,它正在做同样的事情,您将指针指向tmp 指向的第一个内存位置,然后向它添加一个值的偏移量

    【讨论】:

      【解决方案3】:

      otherOffset 以字节为单位,但tmpuint16_t *。如果地址计算为tmp + otherOffset,则通过将otherOffset 视为多个uint16_t 对象而不是字节数来计算总和。因此,为了进行所需的计算,tmp 被转换为uint8_t *。然后(uint8_t *) tmp + otherOffset 以字节为单位进行计算。

      同样,offset 以字节为单位,getVariableAddress(variable) 的类型可能不是指向字节/字符类型的指针(我们不知道,因为您没有向我们展示它的声明),所以演员表确保算术以所需的单位(字节)完成。

      uint16_t * 的强制转换只是将算术结果转换为所需的指针类型以供进一步使用。

      tmp[0]tmp[2] 是下标的普通用法。实际上,他们说,“有一个uint16_t 数组,从tmp 指向的位置开始。给我那个数组中索引为 0 或 2 的元素。”正式地,tmp[i] 定义为*(tmp + i),表示将偏移量i 添加到tmp,然后取消引用结果地址。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-08-24
        • 2021-09-20
        • 1970-01-01
        • 2013-04-14
        • 1970-01-01
        • 2021-07-11
        相关资源
        最近更新 更多