C 使用指针访问长变量中的字节

C 使用指针访问长变量中的字节,c,pointers,pointer-arithmetic,strict-aliasing,C,Pointers,Pointer Arithmetic,Strict Aliasing,我应该创建一个变量 long long hex = 0x1a1b2a2b3a3b4a4bULL; 然后定义4个指向1a1b、2a2b、3a3b和4a4b的指针。然后我打印这些双字节的地址和值 我的方法是创建一个指针 long long *ptr1 = &hex; 然后使用指针算法获得下一个值。我所意识到的是,增加这个指针会使它增加很长的字节,而不是像我需要的那样增加2个字节。创建短指针 short *ptr1 = &hex; 这是我需要的,但我的编译器不允许,因为数据类型不

我应该创建一个变量

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

我的方法是创建一个指针

long long *ptr1 = &hex;
然后使用指针算法获得下一个值。我所意识到的是,增加这个指针会使它增加很长的字节,而不是像我需要的那样增加2个字节。创建短指针

short *ptr1 = &hex;

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

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

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

请不要将其转换为
short*
请参见下面的注释,它们是不兼容的类型。对于一致性代码,只能使用
char*

引用第§6.3.2.3章中的
C11

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

因此,解决方法是使用
char*
并使用指针算法来获得所需的地址


注意:由于所有其他答案都建议使用一种明显错误的方法(将指针指向
short*
,这明显违反了严格的别名),因此让我对我的答案和支持性引用进行一点扩展

引用第§6.5/P7章中的
C11

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

-与对象的有效类型兼容的类型

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

-一种类型,它是与数据的有效类型相对应的有符号或无符号类型 反对,

-一种类型,它是与的限定版本相对应的有符号或无符号类型 对象的有效类型

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

-字符类型

在这种情况下,
short
long
是不可兼容的类型。所以唯一的解决方法就是使用
指针指向
char`type


从问题正文中剪切-粘贴 这是由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;
}
#包括
int main(){
长六角=0x1a1b2a2b3a3b4a4bULL;
字符*ptr=(字符*)&hex;
int i;int j;
对于(i=1,j=0;i<8,j<7;i+=2,j+=2){
printf(“地址%p\n处的0x%hx%hx”,ptr[i],ptr[j],(void*)ptr+i);
}
返回0;
}

您需要强制转换指针以将其分配给其他类型:

short *ptr1 = (short*)&hex;
但是,这样做会导致实现定义的行为,因为这取决于系统的终结性。

添加强制转换:

short *ptr1 = (short*)&hex;
但是,请务必注意平台的安全性。 例如,在x86上,数据存储在小端优先,因此

ptr1[0]
应指向
0x4a4b

还要注意平台的实际大小:long-long至少是64位,short至少是16位。如果要确保类型确实是那些大小,请使用
uint64\u t
uint16\u t
。如果系统上没有任何类型与这些精确大小相匹配,则会出现编译器错误

此外,注意对齐。您可以将
uint64\u t
用作
uint16\u t[4]
,但不能反过来使用,因为
uint16\u t
的地址通常可以除以2,而
uint64\u t
的地址可以除以8


正如所料,有人指出这是未定义的行为。这可能是一个愚蠢的“C课程”作业,其中C没有被完全理解

为了避免UB,您可以使用
联合来解决它:

#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;
}
#包括
联合长部件
{
无符号长整型;
无符号短部件[4];
};
内部主(空)
{
联合长部件试验;
test.total=0x1a1b2a2b3a3b4a4bULL;
对于(int i=0;i<4;++i)
{
无符号短*部分=&test.parts[i];
printf(“地址短于%p:0x%hx\n”,(void*)部分,*部分);
}
返回0;
}
根据C11§6.5.2.3,脚注95:

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


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

请显示一个。或者换句话说:显示代码而不是描述代码。“工作”代码将违反严格的别名规则,因此在技术上是无效的。大多数编译器都会做你期望的事情,但标准并不能保证任何(好的)事情都会发生。@MichaelWalz下次我会记住这一点,我也编辑了我的帖子!嘿,丹尼斯,我从问题中删除了答案,并把它放在了我的答案中(不是针对个人的,因为你接受了我的答案,我认为最好放在那里)。一个问题必须是一个问题