美文网首页
[翻译]C语言中指针和数组是否相等?

[翻译]C语言中指针和数组是否相等?

作者: AprSnow | 来源:发表于2017-02-13 17:18 被阅读23次

    这是一个简单的问题,但总是被忽略。特此翻译了一篇博客,讲的很清楚。这里是博客的原地址Are pointers and arrays equivalent in C?

    C语言中指针和数组是否相等?

    简短的答案:不相等。

    详细的答案:这依赖于如何定义“相等”。指针运算和数组下标是相等的,其他方面指针和数组是不同的。

    一个简单的例子展示相等性:

    #include <stdio.h>
    
    int main()
    {
        char arr[] = "don't panic\n";
        char* ptr = arr;
    
        printf("%c %c\n", arr[4], ptr[4]);
        printf("%c %c\n", *(arr+2), *(ptr+2));
    
        return 0;
    }
    

    输出结果:

    t t
    n n
    

    注意到下标在数组和指针中都能工作,相似地,指针运算在数组和指针中都能用。

    他们如何是不同的?

    在一个很重要和基本的方式中,考虑如下代码:

    char array_place[100] = "dont't panic";
    char* ptr_place = "don't panic";
    
    int main()
    {
        char a = array_place[7];
        char b = ptr_place[7];
    
        return 0;
    }
    

    a赋值时发生了什么?与给b赋值有什么不同?看一下编译器的分解是有用的:
    (Visual C++ 2005,x86,Windows XP)

    char a = array_place[7];
    
    0041137E  mov  al,byte ptr [_array_place+7 (417007h)]
    00411383  mov  byte ptr [a],al
    
    char b = ptr_place[7];
    
    00411386  mov  eax,dword ptr [_ptr_place (417064h)]
    0041138B  mov  cl,byte ptr [eax+7]
    0041138E  mov  byte ptr [b],cl
    

    C语言中数组的语义指示数组名作为数组第一个元素的地址。因此,在给a赋值的过程中,数组的第8个字符被偏移为array_place[7]的值,移动结果地址的内容到寄存器al,然后再赋给a

    另一方面,指针的语义是十分不同的。指针仅仅是一个固定值,持有另一个内部变量的地址。因此,计算字符串第8个字符的偏移,CPU首先复制指针的值给一个寄存器,然后增加寄存器。这多出一个指令。

    一个图形化的解释

    右边那一列是内存地址,每个格子是内存中存储的内容。如图展示的是array_place的第一个单词。
    注意到array_place是内存地址0x417000的一个简单标签,因此访问array_place[7]就是访问内存地址0x417007。如我们所看到的,编译器仅仅用0x417007代替array_place[7],不需要地址的计算。

    而指针的工作方式是不同的:

    ptr_place仅仅是一个变量,变量的值是一个地址。这个地址是一个字符串所在内存位置的第一个字符的地址,比较访问pointer_place[7]的分解列表能够很清楚理解编译为何如此生成编码。

    C语言的变量名仅仅是一个标签

    没有黑过编译器的程序员经常忽视指针,C语言的变量仅仅是内存空间的一个方便的数字字母组成的笔名。如果我们正在写代码,我们应该仅仅创建内存空间的标签,然后访问这些标签,代替硬编码内存值,后者是由编译器来做的。

    实际上地址不是绝对的硬编码,因为存在载入和重分配。但这在我们的讨论之外,这里就不展开细节了。

    标签是编译器在编译时分配的,从这里我们可以看出指针和数组的巨大不同。

    在函数参数中数组被转换成指针

    先看一段代码:

    void foo(char arr_arg[], char* ptr_arg)
    {
        char a = arr_arg[7];
        char b = ptr_arg[7];
    }
    

    问题:在这里访问ab有什么不同吗?

    回答:一点也没有!

    这是编译器的展开:

        char a = arr_arg[7];
    
    00412DCE  mov  eax,dword ptr [arr_arg]
    00412DD1  mov  cl,byte ptr [eax+7]
    00412DD4  mov  byte ptr [a],cl
    
        char b = ptr_arg[7];
    
    00412DD7  mov  eax,dword ptr [ptr_arg]
    00412DDA  mov  cl,byte ptr [eax+7]
    00412DDD  mov  byte ptr [b],cl
    

    这是因为数组在函数的参数中总是倍转换成指针。

    char arr_place[]的参数声明仅仅是char* arr_place的语法糖。

    这里引用K&R2:

    当数组名作为函数参数时,仅仅是初始化元素的位置。在调用函数时,参数是一个局部变量,数组名就是一个指针,一个存储内容为内存地址的变量。

    如果这看起来很奇怪,再仔细想一想。回想一下前面的图。编译器在这里别无选择,数组名作为一个标签在编译时代替要表示的地址。但一个函数在编译时并没有调用,仅在运行时调用,此时在栈中倍看做是一个参数。编译器不能处理数组内部引用作为标签和把他们替换成地址,因为它不知道实际上数组在运行时传入什么。

    相关文章

      网友评论

          本文标题:[翻译]C语言中指针和数组是否相等?

          本文链接:https://www.haomeiwen.com/subject/hqrtwttx.html