为什么转换指针会改变地址处的值?

Faf*_*Dog 0 c printf pointers casting memset

所以我正在做一个练习,看看我是否正确使用了 memset。

这是我编写的原始代码,它应该将一些地址设置为值 50:

int main(){
    int *block1 = malloc(2048);
    memset(block1, 50, 10);
    // int count = 0;
    for (int *iter = block1; (uint8_t *) iter < (uint8_t *)block1 + 10; iter = (int *) ((uint8_t *)iter + 1) ){
        printf("%p : %d\n", iter, *iter);
    }
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我希望内存中的每个地址都存储值 50。但是我的输出是:

(地址:值)

0x14e008800 : 842150450
0x14e008801 : 842150450
0x14e008802 : 842150450
0x14e008803 : 842150450
0x14e008804 : 842150450
0x14e008805 : 842150450
0x14e008806 : 842150450
0x14e008807 : 3289650
0x14e008808 : 12850
0x14e008809 : 50
Run Code Online (Sandbox Code Playgroud)

我被这个问题困扰了一段时间,并尝试了很多事情,直到我随机决定也许我的指针有问题。然后我尝试了 uint8_t 指针。

int main(){
    uint8_t *block1 = malloc(2048);
    memset(block1, 50, 10);
    for (uint8_t  *iter = block1; iter < block1 + 10; iter++ ){
        printf("%p : %d\n", iter, *iter);
    }
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我所做的就是将 block1 变量和 iter 变量的类型更改为 uint8_t 指针而不是 int 指针,我得到了正确的结果!

0x13d808800 : 50
0x13d808801 : 50
0x13d808802 : 50
0x13d808803 : 50
0x13d808804 : 50
0x13d808805 : 50
0x13d808806 : 50
0x13d808807 : 50
0x13d808808 : 50
0x13d808809 : 50
Run Code Online (Sandbox Code Playgroud)

那么我的问题是,为什么会产生如此大的差异?

Ste*_*mit 5

那么我的问题是,为什么会产生如此大的差异?

因为指针的确切类型非常重要。C 中的指针不仅仅是内存地址。指针是内存地址,以及预计在该地址找到什么类型的数据的概念。

如果你写

uint8_t *p;
... p = somewhere ...
printf("%d\n", *p);
Run Code Online (Sandbox Code Playgroud)

然后在最后一行中,*p获取 指向的一个内存字节p

但如果你写

int *p;
... p = somewhere ...
printf("%d\n", *p);
Run Code Online (Sandbox Code Playgroud)

其中,是的,唯一的变化是指针的类型,然后在完全相同的最后一行中,*p现在获取指向的四个p字节的内存,将它们解释为 32 位int。(这假设int您的机器上有四个字节,这在当今很常见。)

当你打电话时

memset(block1, 50, 10);
Run Code Online (Sandbox Code Playgroud)

您要求将内存中的某些(尽管不是全部)单个字节block1设置为 50。

当您使用int指针跨过该内存块,一次获取(正如我们之前所说的)四个字节的内存时,您会得到 4 字节整数,其中每个 4 字节包含值 50。所以您得到的值曾是

(((((50 << 8) | 50) << 8) | 50) << 8) | 50
Run Code Online (Sandbox Code Playgroud)

恰好是 842150450。

或者,换个角度看,如果您将值 842150450 转换为十六进制(以 16 为基数),您会发现它是 0x32323232,其中 0x32 是十六进制值 50,再次表明我们每个字节有四个字节值 50。

现在,到目前为止,这一切都是有道理的,尽管您在第一个程序中如履薄冰。你有过int *iter,但后来你说

for(iter = block1; (uint8_t *) iter < (uint8_t *)block1 + 10; iter = (int *) ((uint8_t *)iter + 1) )
Run Code Online (Sandbox Code Playgroud)

在那个繁琐的增量表达式中

iter = (int *) ((uint8_t *)iter + 1)
Run Code Online (Sandbox Code Playgroud)

您已设法将地址iter仅增加一个字节。通常,我们说

iter = iter + 1
Run Code Online (Sandbox Code Playgroud)

要不就

iter++
Run Code Online (Sandbox Code Playgroud)

这意味着将地址增加iter几个字节,以便它指向int传统数组中的下一个int

按照您的方式进行操作会产生三个影响:

  1. 您正在访问某种int大小为 的子块的滑动窗口block1。也就是说,您获取了int由字节 1、2、3 和 4 构成的一个,然后int获取了由字节 2、3、4 和 5 构成的一个,然后获取了int由字节 3、4、5 和 6 构成的一个,依此类推。字节具有相同的值,您总是得到相同的值,但这是一件奇怪且通常毫无意义的事情。
  2. int您获取的值中有四分之三未对齐。看起来您的处理器让您摆脱了这种情况,但有些处理器会给您一个总线错误或某种其他类型的内存访问异常,因为并不总是允许未对齐的访问。
  3. 您还违反了严格别名的规则。