• C专家编程 第9章 再论数组 9.2 为什么会发生混淆


    为什么会发生混淆
    The C Programming Language第99页的底部是:
    As format parameters in a function definition(作为函数定义的形式参数),
    然后翻到第100页,紧接前句:
    char s[];
    and(和)
    char *s;
    are equivalent(是一样的;) ...
    尤其整句话的重点在于“数组下标表达式总是可以改写为带偏移量的指针表达式”。
    “包含一个通用规则,就是当一个数组名出现在一个表达式中时,它会被转换成一个指向该数组第一个元素的指针。”

    我们可以看见大量的作为函数参数的数组和指针。在这种情况下,两者是可以完全互换的,如下所示:
    char my_array[10];
    char *my_ptr;
    ...
    i = strlen(my_array);
    j = strlen(my_ptr);
    程序员还可以看到许多类似下面的语句:
    printf("%s %s", my_ptr, my_array);
    人们很容易忽视这只是发生在一种特定的上下文环境中,也就是它们作为一个函数调用的参数使用。
    更糟的是,你可以编写如下语句:
    printf("array at location %x holds string %s", a, a);
    在同一个语句中,既把数组名作为一个地址(指针),又把它作为一个字符数组。这条语句之所以
    可行,是因为printf是一个函数,所以数组名实际上是作为指针来传递的。
    char *argv[]和char **argv也是可以互换的。这个之所以成立是因为argv是一个函数的参数,但它仍然诱使程序员错误地总结出“C语言地址运算方法上是一致且规则的”。

    什么时候数组和指针是相同的
    C语言标准对此做了如下声明。
    规则1.表达式中的数组名(与声明不同)被编译器当做一个指向该数组第一个元素的指针。
    规则2.下标总是与指针的偏移量相同。
    规则3.在函数参数的声明中,数组名被编译器当做指向该数组第一个元素的指针。

    对钻牛角尖的人而言,它确实存在几个极少见的例外,就是把数组作为一个整体来考虑。在下列情况下,对数组的引用不能用指向该数组第一个元素的指针来代替:
    *数组作为sizeof()的操作数---显然此时需要的是整个数组的大小,而不是指针所指向的第一个元素的大小; 
    *使用&操作符数组的地址; 
    *数组是一个字符串(或宽字符串)常量初始值

    规则1:“表达式中的数组名”就是指针
    上面的规则1和规则2合在一起理解,就是对数组下标的引用总是可以写成“一个指向数组的起始地址的指针加上偏移量”。
    int a[10], *p, i = 2;
    就可以通过以下任何一种方法来访问a[i]:
    p = a;        p = a;                p = a + i
    p[i];          *(p+i);                *p;
    事实上,可以采用的方法更多。数组引用a[i]在编译时总是被编译器改写成*(a + i)的形式。C语言标准要求编译器必须具备这个概念性的行为。也许遵循这个规则的捷径就是记住方括号[]表示一个取下标操作符,就像加号表示一个加法运算符一样。取下标操作符接受一个整数和一个指向类型T的指针,所产生的结果类型是T,一个在表达式中的数组名于是就成了指针。只要记住:在表达式中,指针和数组是可以互换的。就像加法一样,取下标操作符的操作数是可以交换的。因为它们在编译器里的最终形式都是指针,并且都可以进行取下标操作。
    下面两种形式都是正确的:
    a[6] = ...;
    6[a] = ...; 
    编译器自动把下标值的步长调整到数组元素的大小。对起始地址执行加法操作之前,编译器会负责计算每次增加的步长。这就是为什么指针总是有类型限制,每个指针只能指向一种类型的原因所在---因为编译器需要知道对指针进行解除引用操作时应该取几个字节,以及每个下标的步长应取几个字节。

    规则2:C语言把数组下标作为指针的偏移值
    把数组下标作为指针加偏移量是C语言从BCPL(C语言的祖先)继承过来的技巧。在人们的常规思维中,在运行时增加对C语言下标的范围检查是不切实际的。因为取下标操作只是表示访问该数组,但并不保证一定要访问。而且,程序员完全可以使用指针来访问数组,从而绕过下标操作符。在这种情况下,数组下标范围检测并不能检测所有对数组的访问的情况。事实上,下标范围检测被认为并不值得加入到C语言中。

    步长因子常常是2的乘方,这样编译器在计算时就可以使用快速的左移位操作,而不是缓慢的加法
    运算。然而,迭代一个int数组是人们最容易想到的。如果一个经过良好优化的编译器执行代码分析,并把基本变量放在高速的寄存器中来确认循环是否继续,那么最终在循环中访问指针和数组所产生的代码很可能是相同的。
    C语言把数组下标改写成指针偏移量的根本原因指针和偏移量是底层硬件所使用的基本模型。

    数组访问                                        
    for(i = 0; i < 10; i++) {                  
        a[i] = 0;                              
    }                                         

    指针备选方案1
    p = a;
    for (i = 0; i < 10; i++) {
        p[i] = 0;

    指针备选方案2
    p = a;
    for (i = 0; i < 10; i++) {
        *(p + i) = 0;

    指针备选方案3
    p = a;
    for (i = 0; i < 10; i++) {
        *p++ = 0;

    “可以提取到循环外”表示这个数据不会被循环修改,在每次循环时可不必执行该语句,因此可以加快循环的速度。

    规则3 "作为函数参数的数组名"等同于指针

    术语定义   例子
    形参(parameter)它是一个变量,在函数定义或函数声明的原型中定义。它又称为“形式参数”(formal parameter)

     int power(int base, int n);

    base和n都是形参        

    实参(argument)在实际调用一个函数是所传递给函数的值。它又称为“实际参数”(actual parameter) i = power(10, j); 10和j都是实参,在同一个函数的多次调用时,实参可以不同

    标准规定,作为“类型的数组”的形参的声明应该调整为“类型的指针”。在函数形参定义这个特殊情况下,编译器必须把数组形式改写为指向数组第一个元素的指针形式。编译器指向函数传递数组的地址,而不是整个数组的副本。隐形转换意味着3种形式是完全等同的。因此,在my_function()的调用上,无论实参是数组还是真的指针都是合法的。
    my_function(int *turnip) {
        ...

    my_function(int turnip[]) {
        ...
    }
    my_function(int turnip[100]) {
        ...
    }

  • 相关阅读:
    信号量机制的实现
    Codeforces Round #812 (Div. 2)A.B.C.D
    异常概述
    vue3 + ts + echart 实现柱形图表
    Docker覆盖网络--初步了解
    OLED透明屏控制:引领未来交互技术的创新突破
    PG大小版本升级步骤
    Python 网络爬虫:深入解析 Scrapy
    从油猴脚本管理器的角度审视Chrome扩展
    React常见的一些坑
  • 原文地址:https://blog.csdn.net/weixin_40186813/article/details/126088353