• 《C陷阱和缺陷》-笔记 (3)


    目录

    前言

    一、指针与数组

    1.数组的运作

    2.指针变量

    二、非数组的指针

    1.函数返回字符数

    2.错误原因

    三、作为参数的数组声明

    四、避免“举隅法”

    五、空指针并非空字符串



    前言

    一个句子哪怕其中的每个单词都拼写正确,而且语法也无懈可击,仍然可能有歧义或者并非书写者希望表达的意思。在某些C语言实现中能够正常工作,而在另一些C语言实现中却又不能工作的情形,这属于可移植性方面的问题。


    一、指针与数组

    C语言中指针与数组这两个概念之间的联系是如此密不可分。

    C语言中的数组值得注意的地方有以下两点:
    C语言中只有一维数组,而且数组的大小必须在编译期就作为一个常数确定下来。

    对于一个数组,我们只能够做两件事:

    1.确定该数组的大小,

    2.获得指向该数组下标为0的元素的指针。

    任何一个数组下标运算都等同于一个对应的指针运算,因此我们完全可以依据指针行为定义数组下标的行为。

    1.数组的运作

    要理解C语言中数组的运作机制,我们首先必须理解如何声明一个数组。例如,
    int a [3];
    这个语句声明了a是一个拥有3个整型元素的数组。类似地,
    struct {
                int p [4];
                double x;
    }b[17];


    声明了b是一个拥有17个元素的数组,其中每个元素都是一个结构,

    现在考虑下面的例子,
    int calendar [12][31]; 


    这个语句声明了calendar 是一个数组,该数组拥有12个数组类型的元素,其中每个元素都是一个拥有31个整型元素的数组。

    sizeof (calendar )的值是372(31×12)与sizeof (int)的乘积。

    如果calendar 不是用于sizeof 的操作数,那么calendar 总是被转换成一个指向calendar 数组的起始元素的指针。

    2.指针变量

    任何指针都是指向某种类型的变量,如果有这样的语句:
    int * ip;
    就表明ip是一个指向整型变量的指针。又如果声明,
    int  i;
    那么我们可以将整型变量 i 的地址赋给指针 ip ,就像下面这样:
    ip = &i;
    而且,如果我们给*ip赋值,就能够改变i的取值:
    *ip = 17;

    如果一个指针指向的是数组中的一个元素,那么我们只要给这个指针加1

    如果我们给这个指针减1,得到就是指向该数组中前一个元素的指针

    可以得到:如果ip指向一个整数,那么ip+1指向的是计算机内存中的下一个整数

    如果两个指针指向的是同一个数组中的元素,我们可以把这两个指针相减。这样做是有意义的,例如:
    Int * p = q+ i;

    那么我们可以通过q-p而得到i的值。

    如果我们在应该出现指针的地方,却采用了数组名来替换,那么数组名就被当作指向该数组下标为0的元素的指针。因此如果我们这样写,
    p = a;

    注意:

    p=&a;
    这种写法在ANSIC 中是非法的,因为&a是一个指向数组的指针,而p是一个指向整型变量的指针,它们的类型不匹配。

    如果希望p指向数组a中下标为1的元素,可以这样写:
    p=p+1;

    该语句完全等同于下面的写法:
    p++;

    sizeof (a)的结果是整个数组a的大小,而不是指向数组a的元素的指针的大小。

    可以得出一个推论:

    *a即数组a中下标为0的元素的引用

    二、非数组的指针

    在C语言中,字符串常量代表了一块包括字符串中所有字符以及一个空字符(0)的内存区域的地址

    char * r;
    strcpy (r,s);
    strcat  (r,t);

    我们还应该看到,不仅要让r指向一个地址,而且r所指向的地址处还应该有内存空间可供容纳字符串,

    char r[100]; 
    strcpy (r,s);
    strcat  (r,t); 

    C语言强制要求我们必须声明数组大小为一个常量,因此我们不够确保r足够大。

    1.函数返回字符数

    大多数C语言实现还提供了一个库函数strlen ,该函数返回一个字符串中所包括的字符数。我们就能够像下面这样操作了:
    char* r, *malloc ();
    r= malloc( strlen( s)+ strlen(t));
    strcpy( r, s);
    strcat  (r,t);

    2.错误原因

    这个例子还是错的,原因归纳起来有三个:

    1. malloc 函数有可能无法提供请求的内存,这种情况下malloc 函数会通过返回一个空指针来作为“内存分配失败”事件的信号。


    2. 给r分配的内存在使用完之后应该及时释放。例子中r是作为一个局部变量声明的,因此当离开r作用域时,r自动被释放了。修订后的程序显式地给r分配了内存,为此就必须显式地释放内存。


    3. 前面的例程在调用malloc 函数时并未分配足够的内存。字符串以空字符作为结束标志时。库函数strlen 返回参数中字符串所包括的字符数目,而作为结束标志的空字符并未计算在内。因此,如果strlen (s)的值是n,那么字符串实际需要n+1个字符的空间。所以,我们必须为r多分配一个字符的空间我们就得到正确的结果:


    char *r,malloc();
    r = malloc( strlen(s)) + strlen (t)  + 1);
    if( ! r ){
               complain();
               exit(1);
    }
    strcpy (r,s);
    strcat  (r, t );

    free(r);

    三、作为参数的数组声明

    在C语言中,我们没有办法可以将一个数组作为函数参数直接传递。

    例如,下面的语句:
    char hello[] = "hello";

    声明了hello 是一个字符数组。将该数组作为参数传递给一个函数,
    printf("%s\n",hello);


    实际上将该数组第1个元素的地址作为参数传递给函数的作用完全等效,即:
    printf("s\n",&hello[0]);

    C语言中会自动地将作为参数的数组声明转换为相应的指针声明。这样的写法:
    int strlen( char s[])

    {

    /*具体内容*/
    }

    与下面的写法完全相同:
    int strlen( char* s)
    {
    /具体内容*/
    }

    C程序员经常错误地假设,在其他情形下也会有这种自动地转换

    extern char * hello;
    这个语句与下面的语句有着天渊之别:
    extern char hello [];

    如果一个指针参数并不实际代表一个数组,即使从技术上而言是正确的,采用数组形式的记法经常会起到误导作用。

    一个常见的例子就是函数main的第二个参数:
    main( int argc, char* argv[])
    {
    /*具体内容*1
    }

    这种写法与下面的写法完全等价:
    main( int argc, char* * argv)
    {
    /*具体内容*
    }

    需要注意的是,前一种写法强调的重点在于argv是一个指向某数组的起始元素的指针,该数组的元素为字符指针类型。两种写法是等价,可以任选能清楚反映自己意图的写法。

    四、避免“举隅法”

    C语言中一个常见的“陷阱”:混淆指针与指针所指向的数据。

    例如:
    char * p, *q;
    p = "xyz";

    实际上,p的值是一个指向由x、y、z和\0,4个字符组成的数组的起始元素的指针

    如果我们执行下面的语句:
    p=q
    p和q现在是两个指向内存中同一地址的指针,这个赋值语句并没有同时复制内存中的字符。
    复制指针并不同时复制指针所指向的数据。

    因此,当我们执行完下面的语句之后:
    q[1]='Y';
    q所指向的内存现在存储的是字符串xYz。因为p和q所指向的是同一块内存,所以p指向的内存中存储的当然也是字符串'xYz'。

    五、空指针并非空字符串

    在C语言中将一个整数转换为一个指针,最后得到的结果都取决于具体的C编译器实现。这个特殊情况就是常数0,常数0这个值经常用一个符号来代替:

    #define NULL 0
    当然无论是直接用常数0,还是用符号NULL,效果都是相同的。当常数0被转换为指针使用时,这个指针绝对不能被解除引用(dereference )。

    下面的写法是完全合法的:
    if (p == ((char *)0)


    但是如果要写成这样:
    if (strcmp( p, (char * 0)0 == 0)
    就是非法的了,原因在于库函数strcmp 的实现中会包括查看它的指针参数所指向内存中的内容的操作。


    如果p是一个空指针,即使
    printf(p);
    printf ("%s",p);
    的行为也是未定义的


  • 相关阅读:
    华为云RDS数据库测评:性能超出预期,双11优惠还在继续
    功率放大器在磁性微纳米颗粒微流体操控研究中的应用
    【毕业设计】基于单片机的录音器设计与实现 - 物联网 嵌入式 stm32
    【MySQL】(三)DDL数据库操作——数据库的创建、查看、修改、删除
    Redis第二章_实战篇_短信登录+缓存策略+秒杀+分布式锁>>
    update会锁表吗?
    吃葡萄--用图形解决算法问题(java)
    游戏招商公司如何招聘员工?
    Acwing第 66 场周赛【完结】
    读《反无效努力工作法》
  • 原文地址:https://blog.csdn.net/2402_82385099/article/details/136582326