【问题标题】:Accessing bytes in a long long variable with pointers使用指针访问 long long 变量中的字节
【发布时间】:2017-11-02 15:16:29
【问题描述】:

我应该创建一个变量

long long hex = 0x1a1b2a2b3a3b4a4bULL;

然后定义4个指向1a1b、2a2b、3a3b和4a4b的指针。然后我打印这些双字节的地址和值。

我的方法是创建一个指针

long long *ptr1 = &hex;

然后使用指针算术得到下一个值。我意识到增加这个指针会增加 long long 字节,而不是像我需要的那样增加 2 个字节。创建短指针

short *ptr1 = &hex;

这是我需要的,但我的编译器不会让我这样做,因为数据类型不兼容。我该如何解决?有没有办法创建一个递增 2 个字节的指针并将其分配给更大数据类型的变量?

【问题讨论】:

  • 请显示minimal reproducible example。或者换句话说:展示你的代码而不是描述它。
  • “工作”代码将违反严格的别名规则,因此在技术上是 UB。大多数编译器都会按照您的预期进行,但标准不保证任何事情(好的)都会发生。
  • @MichaelWalz 下次我会记住这一点,我还编辑了我的帖子!
  • 嗨,丹尼斯,我从问题中删除了解决方案,并将其放在了我的回答中(不是私人的,因为你接受了我的回答,我认为最好把它放在那里)。一个问题必须是一个问题,它没有答案的地方。如果您不这么认为,请告诉我。谢谢。

标签: c pointers pointer-arithmetic strict-aliasing


【解决方案1】:

您只能通过兼容类型访问任何变量。

但是,char 指针可用于访问任何类型的变量。

请不要将它转换为short* 请参阅下面的注意,它们不是兼容的类型。您只能将char* 用于符合要求的代码。

引用C11,第 6.3.2.3 章

[...] 当指向对象的指针转换为指向字符类型的指针时, 结果指向对象的最低寻址字节。的连续递增 结果,直到对象的大小,产生指向对象剩余字节的指针。

所以,出路是,使用char * 并使用指针算法来获得所需的地址。


注意:由于所有其他答案都暗示了一个明显错误的方法(将指针转换为 short *,这明显违反了严格的别名),让我扩展一下我的答案和支持引号。

引用C11,第 §6.5/P7 章

一个对象的存储值只能由具有以下之一的左值表达式访问 以下类型:88)

——与对象的有效类型兼容的类型,

——与对象的有效类型兼容的类型的限定版本,

— 有符号或无符号类型,对应于有效类型 对象,

— 有符号或无符号类型,对应于 对象的有效类型,

— 聚合或联合类型,其中包括上述类型之一 成员(递归地包括子聚合或包含联合的成员),或

——一种字符类型。

在这种情况下,shortlong long 不是兼容类型。所以唯一的出路是使用pointer tochar`类型。


从问题正文中剪切-'n-粘贴

这是由 OP 作为更新添加的

编辑: 这是不会导致未定义行为的正确解决方案。 编辑2: 添加内存地址。

#include <stdio.h>
int main() {
    long long hex = 0x1a1b2a2b3a3b4a4bULL;
    char *ptr = (char*)&hex;
    int i; int j;
    for (i = 1, j = 0; i < 8, j < 7; i += 2, j += 2) {
        printf("0x%hx%hx at address %p \n", ptr[i], ptr[j], (void *) ptr+i);
    }
    return 0;
}

【讨论】:

  • 这不是 OP 想要的吗?是否有人认为这是不好/好的做法?
  • @kabanus 这不是练习,使用不同的不兼容类型是错误的。
  • 所以我需要创建 8 个指针?我的任务只需要 4,所以我怀疑他们希望用短指针完成它。你的方式,前 2 个字节看起来像这样:char *ptr1 = (char*)&amp;hex+1;char *ptr2 = (char*)&amp;hex;printf("%hx%hx", *ptr1, *ptr2); 有没有办法格式化效率更高一点?
  • @Dennis 虽然这是正确答案,但您的作业可能需要代码,例如兰亭的回答。请记住它是未定义的行为,并且永远不要在现实世界的软件中使用这样的东西。有关避免未定义行为(但不是实现定义的结果)的方法,请参阅我的答案。
  • @Dennis 不,你不需要 8 个指针。注意部分.. "...结果的连续递增,直到对象的大小,产生指向对象剩余字节的指针。" 所以,你可以只有一个指针并且使用指针算法,您可以获取所需的值。
【解决方案2】:

您需要转换指针以将其分配给不同的类型:

short *ptr1 = (short*)&hex;

但是,这样做会导致实现定义的行为,因为您依赖于系统的字节序。

【讨论】:

  • 结果未定义实现,行为未定义,因为它违反了严格的别名规则。
【解决方案3】:

添加演员表:

short *ptr1 = (short*)&hex;

但是,请务必注意您平台的endianness。 例如,在 x86 上,数据首先存储在小端,所以

ptr1[0] 应该指向0x4a4b

还要注意您平台的实际大小:long long 至少为 64 位,short 至少为 16 位。如果您想确保类型确实是这些大小,请使用uint64_tuint16_t。如果您的系统上没有任何与这些确切大小匹配的类型,您将收到编译器错误。

此外,请注意对齐。您可以将uint64_t 用作uint16_t[4],但不能反过来使用,因为uint16_t 的地址通常可以被2 整除,而uint64_t 的地址可以被8 整除。

Should I worry about the alignment during pointer casting?

【讨论】:

    【解决方案4】:

    正如预期的那样,已经指出这是未定义的行为。这可能是那些没有完全理解 C 的愚蠢的“C 课程”作业之一。

    以防万一您想避免 UB,您可以使用 union 解决它:

    #include <stdio.h>
    
    union longparts
    {
        unsigned long long whole;
        unsigned short parts[4];
    };
    
    int main(void)
    {
        union longparts test;
        test.whole = 0x1a1b2a2b3a3b4a4bULL;
    
        for (int i = 0; i < 4; ++i)
        {
            unsigned short *part = &test.parts[i];
            printf("short at addr %p: 0x%hx\n", (void *)part, *part);
        }
        return 0;
    }
    

    来自 C11 §6.5.2.3,脚注 95:

    如果用于读取联合对象内容的成员与上次用于在对象中存储值的成员不同,则将值的对象表示的适当部分重新解释为对象表示中的对象表示6.2.6 中描述的新类型(有时称为“类型双关”的过程)。这可能是一个陷阱表示。

    因此,在某些情况下,陷阱表示 仍然可能会遇到问题,但至少它不是未定义的。结果是实现定义,例如因为主机的字节序。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-06
      • 2023-03-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多