为什么不建议在 C 中使用指针访问数组
Why its not recommended to use pointer for array access in C
我正在学习 C 编程,我在网上浏览了这个教程,其中指出您应该始终尽可能地使用 [] 运算符而不是指针算法。
https://www.cs.swarthmore.edu/~newhall/unixhelp/C_arrays.html#dynamic
you can use pointer arithmetic (but in general don't)
考虑以下 C 语言代码
int *p_array;
p_array = (int *)malloc(sizeof(int)*50);
for(i=0; i < 50; i++) {
p_array[i] = 0;
}
像下面的代码那样使用指针运算有什么区别(以及为什么不推荐)?
int *p_array;
p_array = (int *)malloc(sizeof(int)*50); // allocate 50 ints
int *dptr = p_array;
for(i=0; i < 50; i++) {
*dptr = 0;
dptr++;
}
在哪些情况下使用指针运算会导致软件出现问题?这是不好的做法还是没有经验的工程师可能没有注意?
在您的示例中,如果没有编译器优化,指针算法可能会更有效,因为只增加指针比在每个循环迭代中计算新偏移量更容易。但是,大多数现代 CPU 都以这样一种方式进行了优化,即使用偏移量访问内存不会导致(显着的)性能损失。
即使您碰巧在指针算法更快的平台上编程,如果您激活编译器优化(大多数编译器上的“-O3”),编译器也可能会使用任何方法最快。
因此,是否使用指针运算主要是个人喜好问题。
使用数组索引而不是指针算法的代码通常更容易理解并且更不容易出错。
不使用指针运算的另一个优点是 pointer aliasing 可能不是什么大问题(因为您使用的指针较少)。这样,编译器可以更自由地优化您的代码(使您的代码更快)。
不推荐此代码:
int *p_array;
p_array = (int *)malloc(sizeof(int)*50); // allocate 50 ints
int *dptr = p_array;
for(i=0; i < 50; i++) {
*dptr = 0;
dptr++;
}
因为 1) 无缘无故你有两个指向同一个地方的不同指针,2) 你不检查 malloc()
的结果——众所周知 return NULL 偶尔, 3) 代码不易阅读,以及 4) 很容易犯下以后很难发现的愚蠢错误。
总而言之,我建议改用这个:
int array[50] = { 0 }; // make sure it's zero-initialized
int* p_array = array; // if you must =)
由于对此似乎完全混淆:
在过去,我们有 16 位 CPU 认为 8088、268 等。
要制定地址,您必须加载段寄存器(16 位寄存器)和地址寄存器。如果访问数组,您可以将数组基址加载到段寄存器中,地址寄存器将是索引。
这些平台的 C 编译器确实存在,但指针算法涉及检查地址是否溢出并在必要时碰撞段寄存器(低效)平面寻址指针在硬件中根本不可能。
快进到 80386 现在我们有一个完整的 32 位space。硬件指针是可能的 索引 + 基址寻址会导致 1 个时钟周期的损失。尽管这些段也是 32 位的,因此即使您是 运行 32 位模式,也可以使用段加载数组以避免这种损失。 368 还将段寄存器的数量增加了 2 个。(不知道为什么英特尔认为这是个好主意)虽然仍然有很多 16 位代码
如今,段寄存器在 64 位模式下被禁用,Base+Index 寻址是免费的。
是否有任何平台可以让平面指针在硬件中胜过数组寻址?嗯,是。 1979 年发布的 Motorola 68000 有一个扁平的 32 位地址 space,没有段,Base + Index 寻址模式比立即寻址产生 8 个时钟周期的损失。因此,如果您正在为 80 年代早期的 Sun station、Apple Lisa 等编程,这可能是相关的。
简而言之。如果你想要一个数组,就使用一个数组。如果您想要指针,请使用指针。不要尝试让你的编译器变聪明。将数组转换为指针的复杂代码极不可能提供任何好处,而且速度可能会变慢。
我正在学习 C 编程,我在网上浏览了这个教程,其中指出您应该始终尽可能地使用 [] 运算符而不是指针算法。
https://www.cs.swarthmore.edu/~newhall/unixhelp/C_arrays.html#dynamic
you can use pointer arithmetic (but in general don't)
考虑以下 C 语言代码
int *p_array;
p_array = (int *)malloc(sizeof(int)*50);
for(i=0; i < 50; i++) {
p_array[i] = 0;
}
像下面的代码那样使用指针运算有什么区别(以及为什么不推荐)?
int *p_array;
p_array = (int *)malloc(sizeof(int)*50); // allocate 50 ints
int *dptr = p_array;
for(i=0; i < 50; i++) {
*dptr = 0;
dptr++;
}
在哪些情况下使用指针运算会导致软件出现问题?这是不好的做法还是没有经验的工程师可能没有注意?
在您的示例中,如果没有编译器优化,指针算法可能会更有效,因为只增加指针比在每个循环迭代中计算新偏移量更容易。但是,大多数现代 CPU 都以这样一种方式进行了优化,即使用偏移量访问内存不会导致(显着的)性能损失。
即使您碰巧在指针算法更快的平台上编程,如果您激活编译器优化(大多数编译器上的“-O3”),编译器也可能会使用任何方法最快。
因此,是否使用指针运算主要是个人喜好问题。
使用数组索引而不是指针算法的代码通常更容易理解并且更不容易出错。
不使用指针运算的另一个优点是 pointer aliasing 可能不是什么大问题(因为您使用的指针较少)。这样,编译器可以更自由地优化您的代码(使您的代码更快)。
不推荐此代码:
int *p_array;
p_array = (int *)malloc(sizeof(int)*50); // allocate 50 ints
int *dptr = p_array;
for(i=0; i < 50; i++) {
*dptr = 0;
dptr++;
}
因为 1) 无缘无故你有两个指向同一个地方的不同指针,2) 你不检查 malloc()
的结果——众所周知 return NULL 偶尔, 3) 代码不易阅读,以及 4) 很容易犯下以后很难发现的愚蠢错误。
总而言之,我建议改用这个:
int array[50] = { 0 }; // make sure it's zero-initialized
int* p_array = array; // if you must =)
由于对此似乎完全混淆:
在过去,我们有 16 位 CPU 认为 8088、268 等。 要制定地址,您必须加载段寄存器(16 位寄存器)和地址寄存器。如果访问数组,您可以将数组基址加载到段寄存器中,地址寄存器将是索引。 这些平台的 C 编译器确实存在,但指针算法涉及检查地址是否溢出并在必要时碰撞段寄存器(低效)平面寻址指针在硬件中根本不可能。
快进到 80386 现在我们有一个完整的 32 位space。硬件指针是可能的 索引 + 基址寻址会导致 1 个时钟周期的损失。尽管这些段也是 32 位的,因此即使您是 运行 32 位模式,也可以使用段加载数组以避免这种损失。 368 还将段寄存器的数量增加了 2 个。(不知道为什么英特尔认为这是个好主意)虽然仍然有很多 16 位代码
如今,段寄存器在 64 位模式下被禁用,Base+Index 寻址是免费的。
是否有任何平台可以让平面指针在硬件中胜过数组寻址?嗯,是。 1979 年发布的 Motorola 68000 有一个扁平的 32 位地址 space,没有段,Base + Index 寻址模式比立即寻址产生 8 个时钟周期的损失。因此,如果您正在为 80 年代早期的 Sun station、Apple Lisa 等编程,这可能是相关的。
简而言之。如果你想要一个数组,就使用一个数组。如果您想要指针,请使用指针。不要尝试让你的编译器变聪明。将数组转换为指针的复杂代码极不可能提供任何好处,而且速度可能会变慢。