为 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
处的对象。因此,如果 ptr
是 0x8000
并且 sizeof (int)
是 4,则 ptr + 1
产生 0x8004
, 而不是 0x8001
.
在我最近回来的一次测试中,它有一个问题,大意是,"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
处的对象。因此,如果 ptr
是 0x8000
并且 sizeof (int)
是 4,则 ptr + 1
产生 0x8004
, 而不是 0x8001
.