为 int* 动态分配 100 个字节,然后尝试使用指针算法为其设置值有什么影响?

What is the effect of dynamically allocating 100 bytes for an int*, and then trying to set values to it using pointer arithmetic?

在我最近回来的一次测试中,它有一个问题,大意是,"assuming this code compiles, what will it do?"

代码:

int *ptr

ptr = (int *) malloc( 25 * sizeof(int)); //100 bytes

*ptr = 'x';
*(ptr + 1) = 'x';
.... //go through all the values from 1 to 99 as well
*(ptr +99) = 'x';

我写了代码 运行 它和结果,用 printf(%d, *x) 打印时是 120,x 的 ascii 值。我知道 int 必须只是被设置为 x,当它被打印为 int 时,ascii 值被打印出来,但是当谈到 malloc 的实际效果是什么,以及所有 *(ptr + i) 确实如此。

malloc() 的实际 效果 是,使语句 *ptr = 'x'; 和后续访问 实际上 有效。

如果没有内存分配,尝试取消引用指针将调用 undefined behavior

也就是说,

  • 在尝试取消引用返回的指针之前,您必须检查 malloc() 是否成功。
  • 指针运算遵循数据类型。所以,像 (ptr + 1) 这样的表达式指向下一个 整数 的内存位置,而不是下一个 字节 的内存。因此,表达式 (ptr + <n>) 的 RHS 的任何 n > 24 都将调用 UB.
  • 25 * sizeof(int) == 100 bytes 的假设在很大程度上是特定于实现的。如果 sizeof(int) 小于 4 个字节,您将最终访问指针算术中的边界内存(即使您将指针别名为 char*,考虑到)。

在 C 语言中,数组和指针非常相似,为了简单起见,在这种情况下将它们视为相同是很方便的。因此,您可以将 malloc 视为动态分配 25 个整数的数组(例如,与动态地说 int ptr[25] 相同),或者您可以将其视为阻塞内存中的 25 个连续整数地址并将它们标记为有效。这样,ptr == &ptr[0]。取消引用运算符 * 表示 'change the value stored at this address',它本质上是 'undoes' & 运算符。所以,*ptr == *(&ptr[0]) == ptr[0]。此命令只是将 ptr[0] 设置为等于 'x',其 ASCII 值为 120(并将打印为 ASCII 值,因为数组的类型为 'int' 而不是 'char').其余的作业也这样做。根据您的编译器和操作系统,超过 ptr + 24 的任何内容都可能会给您带来分段错误或无效写入,因为您只分配了 25 个整数,因此 (ptr+99) 不应该是可写地址。如果您只分配了 25 个插槽,您应该无法编辑 ptr[99]

哎呀,C指针算法是基于定义 *(ptr + i) is ptr[i].

这意味着当您为 25 个整数分配 space 时,所有超过第 24 个元素的访问都将调用未定义的行为 - 您实际上试图访问您不知道它代表什么的内存。

但是如果您使用指向 char(或指向 unsigned char)的指针,则允许访问字节级别的任何对象。所以假设在你的编译器中 sizeof(int) 是 4,这很好:

int *iptr;
char *cptr;
iptr = malloc( 25 * sizeof(int)); //100 bytes since we know that sizeof(int) is 4
cptr = (char *) iptr; // cast of pointer to any to pointer to char is valid
for(int i=0; i<25*sizeof(int); i++) cptr[i] = 'x'; // store chars 'x'
for(int i=0; i<25; i++) {
    printf(" %x", (unsigned int) iptr[i]);  // print the resulting ints in hexa
}
printf("\n");

假设您使用字符的 ASCII 表示(很常见),您应该得到 25 个值都等于 0x78787878,因为 0x78 是 'x' 的 ASCII 码。但是这部分是未指定的标准,只是实现定义。

I am stumped when it comes to what the actual effect of the malloc is

malloc 调用为您的数组分配 space。当您最初声明 ptr 时,它并未初始化为指向有效的内存位置:

     +---+
ptr: |   | ----> ???
     +---+

此时尝试通过 ptr 读取或写入将导致未定义的行为;您的代码可能会彻底崩溃,或者它可能会以某种方式破坏存储,或者它可能 出现 到 运行 没有任何问题。

malloc 调用从堆(a.k.a.,一个动态内存池)分配 space,并将 space 的第一个元素的地址分配给ptr:

     +---+
ptr: |   | ---+
     +---+    |
      ...     |
       +------+
       |
       V
     +---+
     |   | ptr[0]
     +---+
     |   | ptr[1]
     +---+
      ...

请注意,自 1989 年标准以来,malloc 调用上的 (int *) 强制转换就不再是必需的,并且实际上被认为是不好的做法(在 C89 下,它可能会掩盖错误)。 IMO,编写 malloc 调用的最佳方法是

T *p = malloc( N * sizeof *p );

其中 T 是任意类型,N 是您要分配的 T 类型元素的数量。由于 表达式 *p 的类型为 T,因此 sizeof *p 等价于 sizeof (T)

and what all of the *(ptr + i) actually does.

*(ptr + i)等价于ptr[i],所以

*ptr = 'x';
*(ptr + 1) = 'x';

相当于写作

ptr[0] = 'x';
ptr[1] = 'x';

请注意

*(ptr +99) = 'x';

超出了您分配的数组范围;你只为 25 个整数预留了足够的 space。同样,此操作(以及 i 大于 24 的任何操作 *(ptr + i) = 'x';)将导致未定义的行为,并且您的代码可能会崩溃、损坏数据或其他。

指针运算考虑了指向的类型; ptr + 1 生成 下一个整数对象 的地址,紧随 ptr 处的对象。因此,如果 ptr0x8000 并且 sizeof (int) 是 4,则 ptr + 1 产生 0x8004 而不是 0x8001.