精华内容
下载资源
问答
  • C语言中动态数组的构建,c语言中动态数组,c语言动态数组,c语言动态分配数组,c语言动态二维数组,c语言动态数组定义,c语言创建动态数组,c语言动态申请数组,c语言一维动态数组,c语言动态数组长度科技信息. 专题论述...

    C语言中动态数组的构建,c语言中动态数组,c语言动态数组,c语言动态分配数组,c语言动态二维数组,c语言动态数组定义,c语言创建动态数组,c语言动态申请数组,c语言一维动态数组,c语言动态数组长度

    科技信息. 专题论述

    C语言巾动态数组帕构建

    菏泽学院计算机与信息工程系 刘洪霞

    [摘要]在c语言中,数组在使用前必须进行定义,一旦定义了一个数组,系统将为它分配一个所申请大小的空间,该大小固定,以

    后不能改变,称为静态数组。但在编程过程中,有时我们所需的内存空间无法预先确定,对于这个问题,用静态数组的办法很难解

    决。为了解决这个问题,C语言提供了一些内存管理函数,这些内存管理函数结合指针可以按需要动态地分配内存空间,来构建动

    态数组。本文就从动态数组的定义,使用两个方面介绍一下动态数纽是如何构建的。

    [关键词]动态数组静态数纽ma/loc指针

    1.什么是动态数组

    动态数组是相对于静态数组而言。静态数组的长度是预先定义好 数分配到的空间在分配时就已经被初始化为0了。

    的,在整个程序中,一旦给定大小后就无法改变。而动态数组则不然,它

    初始化所分配的内存空间,从而来选择相应的函数。

    可以随程序需要而重新指定大小。动态数组的内存空间是从堆(heap)

    上分配(即动态分配)的。是通过执行代码而为其分配存储空间。当程序 4.具体构建方法

    执行到这些语句时,才为其分配。程序员自己负责释放内存。 以三维整型数组array[n1][n2][n3]为例。

    2.动态数组与静态数组的对比 先遵循从外层到里层,逐层申请的原则:最外层指针是array,它是

    对于静态数组,其创建非常方便,使用完也无需释放,要引用也简

    单,但是创建后无法改变其大小是其致命弱点!对于动态数组,其创建 应为:array=(int”8)calloc(nl,sizeof(int44));

    麻烦,使用完必须由程序员自己释放,否则严重者会引起内存泄露。但

    其使用非常灵活,能根据程序需要动态分配大小。那么如何构建动态数 维指针。所以给array[1申请内存应为:

    组呢,我们应遵循两个原则:(1)申请的时候从外层往里层,逐层申请; for(i=O;i

    (2)释放的时候从里层往外层,逐层释放。对于构建一维动态数组,需要 (

    一维指针;对于二维,则需要一维,二维指针;三维需要一,二,三维指 array[i]=(int”)calloc(n2,sizeof(int+));

    针;依此类推。 )

    3.构建所需函数

    (1)构建时所用的函数及说明如下表所示 是个整型常量。所以给array[][]请内存应为:

    表1 for(i=O;i

    (

    函数原型 返回 功能说明

    for0=0玎

    (

    void4malloc 成功:返回所开辟空间首 向系统申请size字节

    array[il[j]=(int+)calloc(n3,sizeof(int));

    int 地址失败:返回空指针 的堆空间

    (unsignedsize);

    展开全文
  • C语言0长度数组(可变数组/柔性数组)详解

    万次阅读 多人点赞 2017-03-20 19:37:26
    C语言0长度数组(可变数组/柔性数组)详解 CSDN GitHub C语言0长度数组(可变数组/柔性数组)详解 AderXCoding/language/c/zero_length_array 本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议...
    CSDN GitHub
    C语言0长度数组(可变数组/柔性数组)详解 AderXCoding/language/c/zero_length_array


    知识共享许可协议
    本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可, 转载请注明出处, 谢谢合作

    1 零长度数组概念


    众所周知, GNU/GCC 在标准的 C/C++ 基础上做了有实用性的扩展, 零长度数组(Arrays of Length Zero) 就是其中一个知名的扩展.

    多数情况下, 其应用在变长数组中, 其定义如下

    struct Packet
    {
        int state;
        int len;
        char cData[0]; //这里的0长结构体就为变长结构体提供了非常好的支持
    };

    首先对 0长度数组, 也叫柔性数组 做一个解释 :

    • 用途 : 长度为0的数组的主要用途是为了满足需要变长度的结构体

    • 用法 : 在一个结构体的最后, 申明一个长度为0的数组, 就可以使得这个结构体是可变长的. 对于编译器来说, 此时长度为0的数组并不占用空间, 因为数组名本身不占空间, 它只是一个偏移量, 数组名这个符号本身代表了一个不可修改的地址常量

    (注意 : 数组名永远都不会是指针!), 但对于这个数组的大小, 我们可以进行动态分配

    注意 :如果结构体是通过calloc、malloc或 者new等动态分配方式生成,在不需要时要释放相应的空间。

    优点 :比起在结构体中声明一个指针变量、再进行动态分 配的办法,这种方法效率要高。因为在访问数组内容时,不需要间接访问,避免了两次访存。

    缺点 :在结构体中,数组为0的数组必须在最后声明,使 用上有一定限制。

    对于编译器而言, 数组名仅仅是一个符号, 它不会占用任何空间, 它在结构体中, 只是代表了一个偏移量, 代表一个不可修改的地址常量!

    2 0长度数组的用途


    我们设想这样一个场景, 我们在网络通信过程中使用的数据缓冲区, 缓冲区包括一个len字段和data字段, 分别标识数据的长度和传输的数据, 我们常见的有几种设计思路

    • 定长数据缓冲区, 设置一个足够大小 MAX_LENGTH 的数据缓冲区

    • 设置一个指向实际数据的指针, 每次使用时, 按照数据的长度动态的开辟数据缓冲区的空间.

    我们从实际场景中应用的设计来考虑他们的优劣. 主要考虑的有, 缓冲区空间的开辟, 释放和访问.

    2.1 定长包(开辟空间, 释放, 访问)


    比如我要发送 1024 字节的数据, 如果用定长包, 假设定长包的长度 MAX_LENGTH2048, 就会浪费 1024 个字节的空间, 也会造成不必要的流量浪费.

    • 数据结构定义
    //  定长缓冲区
    struct max_buffer
    {
        int     len;
        char    data[MAX_LENGTH];
    };
    • 数据结构大小

    考虑对齐, 那么数据结构的大小 >= sizeof(int) + sizeof(char) * MAX_LENGTH

    由于考虑到数据的溢出, 变长数据包中的 data 数组长度一般会设置得足够长足以容纳最大的数据, 因此 max_buffer 中的 data 数组很多情况下都没有填满数据, 因此造成了浪费

    • 数据包的构造

    假如我们要发送 CURR_LENGTH = 1024 个字节, 我们如何构造这个数据包呢:

    一般来说, 我们会返回一个指向缓冲区数据结构 max_buffer 的指针.

        ///  开辟
        if ((mbuffer = (struct max_buffer *)malloc(sizeof(struct max_buffer))) != NULL)
        {
            mbuffer->len = CURR_LENGTH;
            memcpy(mbuffer->data, "Hello World", CURR_LENGTH);
    
    
            printf("%d, %s\n", mbuffer->len, mbuffer->data);
        }
    • 访问

    这段内存要分两部分使用

    前部分 4 个字节 p->len, 作为包头(就是多出来的那部分),这个包头是用来描述紧接着包头后面的数据部分的长度,这里是 1024, 所以前四个字节赋值为 1024 (既然我们要构造不定长数据包,那么这个包到底有多长呢,因此,我们就必须通过一个变量来表明这个数据包的长度,这就是len的作用),

    而紧接其后的内存是真正的数据部分, 通过 p->data, 最后, 进行一个 memcpy() 内存拷贝, 把要发送的数据填入到这段内存当中

    • 释放

    那么当使用完毕释放数据的空间的时候, 直接释放就可以了

        /// 销毁
        free(mbuffer);
        mbuffer = NULL;
    • 小结

      1. 使用定长数组, 作为数据缓冲区, 为了避免造成缓冲区溢出, 数组的大小一般设为足够的空间 MAX_LENGTH, 而实际使用过程中, 达到 MAX_LENGTH 长度的数据很少, 那么多数情况下, 缓冲区的大部分空间都是浪费掉的.

      2. 但是使用过程很简单, 数据空间的开辟和释放简单, 无需程序员考虑额外的操作

    2.2 指针数据包(开辟空间, 释放, 访问)


    如果你将上面的长度为 MAX_LENGTH 的定长数组换为指针, 每次使用时动态的开辟 CURR_LENGTH 大小的空间, 那么就避免造成 MAX_LENGTH - CURR_LENGTH 空间的浪费, 只浪费了一个指针域的空间.

    • 数据包定义
    struct point_buffer
    {
        int     len;
        char    *data;
    };
    • 数据结构大小

    考虑对齐, 那么数据结构的大小 >= sizeof(int) + sizeof(char *)

    • 空间分配

    但是也造成了使用在分配内存时,需采用两步

        // =====================
        // 指针数组  占用-开辟-销毁
        // =====================
        ///  占用
        printf("the length of struct test3:%d\n",sizeof(struct point_buffer));
        ///  开辟
        if ((pbuffer = (struct point_buffer *)malloc(sizeof(struct point_buffer))) != NULL)
        {
            pbuffer->len = CURR_LENGTH;
            if ((pbuffer->data = (char *)malloc(sizeof(char) * CURR_LENGTH)) != NULL)
            {
                memcpy(pbuffer->data, "Hello World", CURR_LENGTH);
    
    
                printf("%d, %s\n", pbuffer->len, pbuffer->data);
            }
        }
    1. 首先, 需为结构体分配一块内存空间;

    2. 其次再为结构体中的成员变量分配内存空间.

    这样两次分配的内存是不连续的, 需要分别对其进行管理. 当使用长度为的数组时, 则是采用一次分配的原则, 一次性将所需的内存全部分配给它.

    • 释放

    相反, 释放时也是一样的.

        /// 销毁
        free(pbuffer->data);
        free(pbuffer);
        pbuffer = NULL;
    • 小结

      1. 使用指针结果作为缓冲区, 只多使用了一个指针大小的空间, 无需使用 MAX_LENGTH 长度的数组, 不会造成空间的大量浪费.

      2. 但那是开辟空间时, 需要额外开辟数据域的空间, 施放时候也需要显示释放数据域的空间, 但是实际使用过程中, 往往在函数中开辟空间, 然后返回给使用者指向 struct point_buffer 的指针, 这时候我们并不能假定使用者了解我们开辟的细节, 并按照约定的操作释放空间, 因此使用起来多有不便, 甚至造成内存泄漏

    2.3 变长数据缓冲区(开辟空间, 释放, 访问)


    定长数组使用方便, 但是却浪费空间, 指针形式只多使用了一个指针的空间, 不会造成大量空间分浪费, 但是使用起来需要多次分配, 多次释放, 那么有没有一种实现方式能够既不浪费空间, 又使用方便的呢?

    GNU C 的0长度数组, 也叫变长数组, 柔性数组就是这样一个扩展. 对于0长数组的这个特点,很容易构造出变成结构体,如缓冲区,数据包等等:

    • 数据结构定义
    //  0长度数组
    struct zero_buffer
    {
        int     len;
        char    data[0];
    };
    • 数据结构大小

    这样的变长数组常用于网络通信中构造不定长数据包, 不会浪费空间浪费网络流量, 因为char data[0]; 只是个数组名, 是不占用存储空间的,

    sizeof(struct zero_buffer) = sizeof(int)

    • 开辟空间

    那么我们使用的时候, 只需要开辟一次空间即可

        ///  开辟
        if ((zbuffer = (struct zero_buffer *)malloc(sizeof(struct zero_buffer) + sizeof(char) * CURR_LENGTH)) != NULL)
        {
            zbuffer->len = CURR_LENGTH;
            memcpy(zbuffer->data, "Hello World", CURR_LENGTH);
    
    
            printf("%d, %s\n", zbuffer->len, zbuffer->data);
        }
    • 释放空间

    释放空间也是一样的, 一次释放即可

        ///  销毁
        free(zbuffer);
        zbuffer = NULL;

    2.4 总结

    // zero_length_array.c
    #include <stdio.h>
    #include <stdlib.h>
    
    
    #define MAX_LENGTH      1024
    #define CURR_LENGTH      512
    
    //  0长度数组
    struct zero_buffer
    {
        int     len;
        char    data[0];
    }__attribute((packed));
    
    
    //  定长数组
    struct max_buffer
    {
        int     len;
        char    data[MAX_LENGTH];
    }__attribute((packed));
    
    
    //  指针数组
    struct point_buffer
    {
        int     len;
        char    *data;
    }__attribute((packed));
    
    int main(void)
    {
        struct zero_buffer  *zbuffer = NULL;
        struct max_buffer   *mbuffer = NULL;
        struct point_buffer *pbuffer = NULL;
    
    
        // =====================
        // 0长度数组  占用-开辟-销毁
        // =====================
        ///  占用
        printf("the length of struct test1:%d\n",sizeof(struct zero_buffer));
        ///  开辟
        if ((zbuffer = (struct zero_buffer *)malloc(sizeof(struct zero_buffer) + sizeof(char) * CURR_LENGTH)) != NULL)
        {
            zbuffer->len = CURR_LENGTH;
            memcpy(zbuffer->data, "Hello World", CURR_LENGTH);
    
    
            printf("%d, %s\n", zbuffer->len, zbuffer->data);
        }
        ///  销毁
        free(zbuffer);
        zbuffer = NULL;
    
    
        // =====================
        // 定长数组  占用-开辟-销毁
        // =====================
        ///  占用
        printf("the length of struct test2:%d\n",sizeof(struct max_buffer));
        ///  开辟
        if ((mbuffer = (struct max_buffer *)malloc(sizeof(struct max_buffer))) != NULL)
        {
            mbuffer->len = CURR_LENGTH;
            memcpy(mbuffer->data, "Hello World", CURR_LENGTH);
    
    
            printf("%d, %s\n", mbuffer->len, mbuffer->data);
        }
        /// 销毁
        free(mbuffer);
        mbuffer = NULL;
    
        // =====================
        // 指针数组  占用-开辟-销毁
        // =====================
        ///  占用
        printf("the length of struct test3:%d\n",sizeof(struct point_buffer));
        ///  开辟
        if ((pbuffer = (struct point_buffer *)malloc(sizeof(struct point_buffer))) != NULL)
        {
            pbuffer->len = CURR_LENGTH;
            if ((pbuffer->data = (char *)malloc(sizeof(char) * CURR_LENGTH)) != NULL)
            {
                memcpy(pbuffer->data, "Hello World", CURR_LENGTH);
    
    
                printf("%d, %s\n", pbuffer->len, pbuffer->data);
            }
        }
        /// 销毁
        free(pbuffer->data);
        free(pbuffer);
        pbuffer = NULL;
    
    
        return EXIT_SUCCESS;
    }

    运行结果

    • 长度为0的数组并不占有内存空间, 而指针方式需要占用内存空间.

    • 对于长度为0数组, 在申请内存空间时, 采用一次性分配的原则进行; 对于包含指针的结构体, 才申请空间时需分别进行, 释放时也需分别释放.

    • 对于长度为的数组的访问可采用数组方式进行

    3 GNU Document中 变长数组的支持


    参见

    6.17 Arrays of Length Zero

    C Struct Hack – Structure with variable length array

    C90 之前, 并不支持0长度的数组, 0长度数组是 GNU C 的一个扩展, 因此早期的编译器中是无法通过编译的

    对于 GNU C 增加的扩展, GCC 提供了编译选项来明确的标识出他们

    1、-pedantic 选项,那么使用了扩展语法的地方将产生相应的警告信息

    2、-Wall 使用它能够使GCC产生尽可能多的警告信息

    3、-Werror, 它要求GCC将所有的警告当成错误进行处理

    // 1.c
    #include <stdio.h>
    #include <stdlib.h>
    
    
    int main(void)
    {
        char a[0];
        printf("%ld", sizeof(a));
        return EXIT_SUCCESS;
    }

    我们来编译

    gcc 1.c -Wall   # 显示所有警告
    #none warning and error
    
    gcc 1.c -Wall -pedantic  # 对GNU C的扩展显示警告
    1.c: In function ‘main’:
    1.c:7: warning: ISO C forbids zero-size array ‘a’
    
    
    gcc 1.c -Werror -Wall -pedantic # 显示所有警告同时GNU C的扩展显示警告, 将警告用error显示
    cc1: warnings being treated as errors
    1.c: In function ‘main’:
    1.c:7: error: ISO C forbids zero-size array ‘a’

    运行结果

    0长度数组其实就是灵活的运用的数组指向的是其后面的连续的内存空间

    struct buffer
    {
        int     len;
        char    data[0];
    };

    在早期没引入0长度数组的时候, 大家是通过定长数组和指针的方式来解决的, 但是

    • 定长数组定义了一个足够大的缓冲区, 这样使用方便, 但是每次都造成空间的浪费
    • 指针的方式, 要求程序员在释放空间是必须进行多次的free操作, 而我们在使用的过程中往往在函数中返回了指向缓冲区的指针, 我们并不能保证每个人都理解并遵从我们的释放方式

    所以 GNU 就对其进行了0长度数组的扩展. 当使用data[0]的时候, 也就是0长度数组的时候,0长度数组作为数组名, 并不占用存储空间.

    C99之后,也加了类似的扩展,只不过用的是 char payload[]这种形式(所以如果你在编译的时候确实需要用到-pedantic参数,那么你可以将char payload[0]类型改成char payload[], 这样就可以编译通过了,当然你的编译器必须支持C99标准的,如果太古老的编译器,那可能不支持了)

    // 2.c payload
    #include <stdio.h>
    #include <stdlib.h>
    
    struct payload
    {
        int   len;
        char  data[];
    };
    
    int main(void)
    {
        struct payload pay;
        printf("%ld", sizeof(pay));
        return EXIT_SUCCESS;
    }

    使用 -pedantic 编译后, 不出现警告, 说明这种语法是 C 标准的

    gcc 2.c -pedantic -std=c99

    2

    所以结构体的末尾, 就是指向了其后面的内存数据。因此我们可以很好的将该类型的结构体作为数据报文的头格式,并且最后一个成员变量,也就刚好是数据内容了.

    GNU手册还提供了另外两个结构体来说明,更容易看懂意思:

    struct f1 {
        int x;
        int y[];
    } f1 = { 1, { 2, 3, 4 } };
    
    struct f2 {
        struct f1 f1;
        int data[3];
    } f2 = { { 1 }, { 5, 6, 7 } };

    我把f2里面的2,3,4改成了5,6,7以示区分。如果你把数据打出来。即如下的信息:

    f1.x = 1
    f1.y[0] = 2
    f1.y[1] = 3
    f1.y[2] = 4

    也就是f1.y指向的是{2,3,4}这块内存中的数据。所以我们就可以轻易的得到,f2.f1.y指向的数据也就是正好f2.data的内容了。打印出来的数据:

    f2.f1.x = 1
    f2.f1.y[0] = 5
    f2.f1.y[1] = 6
    f2.f1.y[2] = 7

    如果你不是很确认其是否占用空间. 你可以用sizeof来计算一下。就可以知道sizeof(struct f1)=4,也就是int y[]其实是不占用空间的。但是这个0长度的数组,必须放在结构体的末尾。如果你没有把它放在末尾的话。编译的时候,会有如下的错误:

    main.c:37:9: error: flexible array member not at end of struct
         int y[];
             ^

    到这边,你可能会有疑问,如果将struct f1中的int y[]替换成int *y,又会是如何?这就涉及到数组和指针的问题了. 有时候吧,这两个是一样的,有时候又有区别。

    首先要说明的是,支持0长度数组的扩展,重点在数组,也就是不能用int *y指针来替换。sizeof的长度就不一样了。把struct f1改成这样:

    struct f3 {
        int x;
        int *y;
    };

    在32/64位下, int均是4个字节, sizeof(struct f1)=4,而sizeof(struct f3)=16

    因为 int *y 是指针, 指针在64位下, 是64位的, sizeof(struct f3) = 16, 如果在32位环境的话, sizeof(struct f3) 则是 8 了, sizeof(struct f1) 不变. 所以 int *y 是不能替代 int y[] 的.

    代码如下

    // 3.c
    #include <stdio.h>
    #include <stdlib.h>
    
    
    struct f1 {
        int x;
        int y[];
    } f1 = { 1, { 2, 3, 4 } };
    
    struct f2 {
        struct f1 f1;
        int data[3];
    } f2 = { { 1 }, { 5, 6, 7 } };
    
    
    struct f3
    {
        int x;
        int *y;
    };
    
    int main(void)
    {
        printf("sizeof(f1) = %d\n", sizeof(struct f1));
        printf("sizeof(f2) = %d\n", sizeof(struct f2));
        printf("szieof(f3) = %d\n\n", sizeof(struct f3));
    
        printf("f1.x = %d\n", f1.x);
        printf("f1.y[0] = %d\n", f1.y[0]);
        printf("f1.y[1] = %d\n", f1.y[1]);
        printf("f1.y[2] = %d\n", f1.y[2]);
    
    
        printf("f2.f1.x = %d\n", f1.x);
        printf("f2.f1.y[0] = %d\n", f2.f1.y[0]);
        printf("f2.f1.y[1] = %d\n", f2.f1.y[1]);
        printf("f2.f1.y[2] = %d\n", f2.f1.y[2]);
    
        return EXIT_SUCCESS;
    }

    运行结果

    4 0长度数组的其他特征


    4.1 为什么0长度数组不占用存储空间


    参见

    结构体中的指针与零长度数组

    GNU C中的零长度数组

    0长度数组与指针实现有什么区别呢, 为什么0长度数组不占用存储空间呢?

    其实本质上涉及到的是一个C语言里面的数组和指针的区别问题. char a[1]里面的achar *bb相同吗?

    《 Programming Abstractions in C》(Roberts, E. S.,机械工业出版社,2004.6)82页里面说

    “arr is defined to be identical to &arr[0]”.

    也就是说,char a[1]里面的a实际是一个常量,等于&a[0]。而char *b是有一个实实在在的指针变量b存在。 所以,a=b是不允许的,而b=a是允许的。 两种变量都支持下标式的访问,那么对于a[0]和b[0]本质上是否有区别?我们可以通过一个例子来说明。

    参见如下两个程序 gdb_zero_length_array.cgdb_zero_length_array.c

    //  gdb_zero_length_array.c
    #include <stdio.h>
    #include <stdlib.h>
    
    struct str
    {
        int len;
        char s[0];
    };
    
    struct foo
    {
        struct str *a;
    };
    
    int main(void)
    {
        struct foo f = { NULL };
    
        printf("sizeof(struct str) = %d\n", sizeof(struct str));
    
        printf("before f.a->s.\n");
        if(f.a->s)
        {
            printf("before printf f.a->s.\n");
            printf(f.a->s);
            printf("before printf f.a->s.\n");
        }
    
        return EXIT_SUCCESS;
    }

    测试结果

    //  gdb_pzero_length_array.c
    #include <stdio.h>
    #include <stdlib.h>
    
    struct str
    {
        int len;
        char *s;
    };
    
    struct foo
    {
        struct str *a;
    };
    
    int main(void)
    {
        struct foo f = { NULL };
    
        printf("sizeof(struct str) = %d\n", sizeof(struct str));
    
        printf("before f.a->s.\n");
    
        if (f.a->s)
        {
            printf("before printf f.a->s.\n");
            printf(f.a->s);
            printf("before printf f.a->s.\n");
        }
    
        return EXIT_SUCCESS;
    }

    测试结果

    可以看到这两个程序虽然都存在访问异常, 但是段错误的位置却不同

    我们将两个程序编译成汇编, 然户 diff 查看他们的汇编代码有何不同

    gcc -S gdb_zero_length_array.c -o gdb_test.s
    gcc -S gdb_pzero_length_array.c -o gdb_ptest
    diff gdb_test.s gdb_ptest.s
    
    1c1
    <   .file   "gdb_zero_length_array.c"
    ---
    >   .file   "gdb_pzero_length_array.c"
    23c23
    <   movl    $4, %esi
    ---
    >   movl    $16, %esi
    30c30
    <   addq    $4, %rax
    ---
    >   movq    8(%rax), %rax
    36c36
    <   addq    $4, %rax
    ---
    >   movq    8(%rax), %rax
    #    printf("sizeof(struct str) = %d\n", sizeof(struct str));
    23c23
    <   movl    $4, %esi    #printf("sizeof(struct str) = %d\n", sizeof(struct str));
    ---
    >   movl    $16, %esi  #printf("sizeof(struct str) = %d\n", sizeof(struct str));

    从64位系统中, 汇编我们看出, 变长数组结构的大小为4, 而指针形式的结构大小为16

    f.a->s
    30c30/36c36
    <   addq    $4, %rax
    ---
    >   movq    8(%rax), %rax

    可以看到有

    • 对于 char s[0] 来说, 汇编代码用了 addq 指令, addq $4, %rax

    • 对于 char*s 来说,汇编代码用了 movq 指令, movq 8(%rax), %rax

    addq%rax + sizeof(struct str), 即str结构的末尾即是char s[0]的地址, 这一步只是拿到了其地址, 而 movq 则是把地址里的内容放进去, 因此有时也被翻译为leap指令, 参见下一列子

    从这里可以看到, 访问成员数组名其实得到的是数组的相对地址, 而访问成员指针其实是相对地址里的内容(这和访问其它非指针或数组的变量是一样的)

    • 访问相对地址,程序不会crash,但是,访问一个非法的地址中的内容,程序就会crash。

    有时候

    // 4-1.c
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
    
        char *a;
        printf("%p\n", a);
    
        return EXIT_SUCCESS;
    }
    4-2.c
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
    
        char a[0];
        printf("%p\n", a);
    
        return EXIT_SUCCESS;
    }
    $ diff 4-1.s 4-2.s
    1c1
    <       .file   "4-1.c"
    ---
    >       .file   "4-2.c"
    13c13
    <       subl    $16, %esp
    ---
    >       subl    $32, %esp
    15c15
    <       leal    16(%esp), %eax
    ---
    >       movl    28(%esp), %eax
    • 对于 char a[0] 来说, 汇编代码用了 leal 指令, leal 16(%esp), %eax

    • 对于 char *a 来说,汇编代码用了 movl 指令, movl 28(%esp), %eax

    4.2 地址优化


    // 5-1.c
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
    
        char a[0];
        printf("%p\n", a);
    
        char b[0];
        printf("%p\n", b);
    
        return EXIT_SUCCESS;
    }

    5

    由于0长度数组是 GNU C 的扩展, 不被标准库任可, 那么一些巧妙编写的诡异代码, 其执行结果就是依赖于编译器和优化策略的实现的.

    比如上面的代码, a和b的地址就会被编译器优化到一处, 因为a[0] 和 b[0] 对于程序来说是无法使用的, 这让我们想到了什么?

    编译器对于相同字符串常量, 往往地址也是优化到一处, 减少空间占用

    //  5-2.c
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
    
        const char *a = "Hello";
        printf("%p\n", a);
    
        const char *b = "Hello";
        printf("%p\n", b);
    
        const char c[] = "Hello";
        printf("%p\n", c);
    
        return EXIT_SUCCESS;
    }

    5-2

    参考


    结构体中的指针与零长度数组

    零长度数组的妙用

    C语言中长度为0的数组

    C/C++ 中的0长数组(柔性数组)

    GNU C中的零长度数组

    C语言变长数组data[0]【总结】

    长度为0的数组——C语言的非标准用法之一

    长度为0的数组的size为什么不一定是0?

    有关C语言占位符

    C语言技巧之长度为0的数组

    零长度数组使用

    使用零长度数组

    零长度数组

    C/C++ 中长度为0的数组

    GCC 中零长数组与变长数组

    零长度数组的妙用

    Multiple 0-length arrays have the same address?



    知识共享许可协议本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可, 转载请注明出处, 谢谢合作.

    展开全文
  • 两个数组a、b,各有10个元素,将它们对应地逐个相比。如果a数组中的元素大于b数组中相应元素的数目多于b数组与中大于a数组中相应元素的数目(例如,a[i]>b[i]6次,b[i]>a[i]3次,其中i每次为不同的值),则...

    有两个数组a、b,各有10个元素,将它们对应地逐个相比。如果a数组中的元素大于b数组中相应元素的数目多于b数组与中大于a数组中相应元素的数目(例如,a[i]>b[i]6次,b[i]>a[i]3次,其中i每次为不同的值),则认为a数组大于b数组,并分别统计出两个数组相应元素大于、等于和小于的次数。

    #include<stdio.h>
    int cmp(int x, int y);
    int main()
    {
    	int a[10], b[10], i, large = 0, small = 0, equal = 0;
    	puts("输入10个元素的数组a");
    	for (i = 0; i < 10; i++)
    		scanf("%d", &a[i]);
    	puts("输入10个元素的数组b");
    	for (i = 0; i < 10; i++)
    		scanf("%d", &b[i]);
    	for (i = 0; i < 10; i++) {
    		if (cmp(a[i], b[i]) == 1) large++;
    		else if (cmp(a[i], b[i]) == -1)small++;
    		else equal++;
    	}
    	if (large > small) printf("a大于b\n");
    	else if (large < small) printf("a小于b\n");
    	else printf("a等于b\n");
    	printf("a大于b元素的个数为%d\n", large);
    	printf("a小于b元素的个数为%d\n", small);
    	printf("a等于b元素的个数为%d\n", equal);
    }
    int cmp(int x, int y)
    {
    	int ret;
    	if (x > y) ret = 1;
    	else if (x < y) ret = -1;
    	else ret = 0;
    	return ret;
    }
    
    展开全文
  • 详解C语言中的数组指针与指针数组

    万次阅读 多人点赞 2018-05-06 21:52:39
    ·详解数组指针与指针数组 ·数组指针 一、区分 首先我们需要了解什么是数组指针以及什么是...所以,int (*p)[5]即为一个数组指针。int *p[5]则是一个大小为5且存放整型指针的数组。 二、数组元素的指针 1.定...

    ·详解数组指针与指针数组

    ·数组指针

    一、区分

    首先我们需要了解什么是数组指针以及什么是指针数组,如下图:

    int *p[5];
    int (*p)[5];

    数组指针的意思即为通过指针引用数组,p先和*结合,说明了p是一个指针变量,指向一个大小为5的数组。所以,int (*p)[5]即为一个数组指针。int *p[5]则是一个大小为5且存放整型指针的数组。

    二、数组元素的指针

    1.定义

    指针变量既然可以指向变量,同样的,也能指向数组元素,因此,数组元素的指针就是数组元素的地址。

    它的写法为:

    int *p=arr;
    int *p=&a[0];

    这边我们需要再次明确,数组名并不代表整个数组,只是代表数组首元素的地址,因此上面两个语句是一样的。

    2.运算

    由于指针指向的是一个地址,因此数组指针也同样可以进行相关运算;例如指针的加减可以实现指针指向数组上一个或者下一个元素的功能。这边需要说明,数组指针中进行乘法和除法是没有意义的。

    如下图所示:

    在定义指针变量的时候需要定义类型,如果指针p指向了一个数组中的一个元素,那么p+1并不是将地址加上1,而是系统判定类型之后加上一个数组元素所占用的字节数(即为p+1*d)。

    3.通过指针引用数组元素

    代码如下图:

    #include<stdio.h>
    int main()
    {
    	int a[10]={1,2,3,4,5,6,7,8,9,0};
    	int *p;
    	for(p=a ; p<(a+10) ; p++)
    	{
    		printf("%d ",*p);
    	}
    	printf("\n");
    	return 0;
    }

    先让指针p指向a数组的首元素,打印*p(即指向数组的值),然后执行p++,使得p指向下一个元素,直到输出数组的十个元素为止。

    三、通过指针引用多维数组

    1.多维数组元素的地址

    我们以二维数组为例,首先需要明确一点的是二维数组的首元素地址并非一个单个元素,而是首行的地址,如下图:

    下面我们列出相关地址的表示方式:

     

    表示形式 含义
    a     二维数组名,指向a[0]
    a[0], *(a+0), *a 0行0列元素地址
    a+1, &a[1] 1行首地址
    a[1], *(a+1) a[1][0]的地址
    a[1]+2, *(a+1)+2, &a[1][2]

    a[1][2]的地址

    *(a[1]+2), *(*(a+1)+2), a[1][2] a[1][2]的值

    上图都是二维数组中地址的不同表示形式。

    2.指向多维数组的指针变量

     

    输出每一个值依然可以像一维数组一样,但这里我们可以介绍一种新的方法:

    int main()
    {
    	int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    	int (*p)[4];
    	int i=0;
    	p=a;
    	printf("%d\n",(*p)[11]);
    	return 0;
    }

    就像我们前面的例子一样,int(*p)[12]表示定义一个指针变量,它指向一个包含12个整型元素的一堆数组。我们将其设定为12便可以存下整个a数组,当然也可以存一行4个,最后如果输出的话就方便了许多。

    四、数组指针作函数参数

    下面是几种传参的方式:

    void test()
    {
     int arr[3][5] = {0};
     print(arr);
    }
    void print(int arr[3][5])
    {}
    void print(int arr[][5])
    {}
    void print(int **arr)
    {}
    void print(int (*arr)[5])
    {}

    我们可以看出第三种方式明显是不行的,这边引用了一个二级指针,但是我们上面提到过数组的地址应该放到数组指针中去。而第四种方法就是我们上面提到过的,而这种方式是可行的。

    下面是一个一级指针传参的例子:

    #include <stdio.h>
    void print(int *p, int sz)
    {
     int i = 0;
     for(i=0; i<sz; i++)
     {
     printf("%d\n", *(p+i));
     }
    }
    int main()
    {
     int arr[10] = {1,2,3,4,5,6,7,8,9};
     int *p = arr;
     int sz = sizeof(arr)/sizeof(arr[0]);
     print(p, sz);
     return 0;
    }

    ·指针数组

    一、指针数组

    1.定义

     

    int *p[5];

    上面使我们刚开始举出的例子,这就是一个最简单的指针数组。因此我们可以得出指针数组的定义。指针数组:一个数组的元素均为指针类型数据,称为指针数组。

    假设我们定义一个数组,各个元素指向不同的字符串,如下图:

    上图所示,我们定义一个指针数组arr,然后把各字符串的地址赋给各个元素,即arr[0]和arr[1]。

    通过一个简单的函数就可以进行输出了。

    二、指向指针数据的指针

    首先我们可以定义一个指向指针数据的指针变量:

    char **p;
    

    为了方便我们理解,其实**p也就等于*(*p)。*p表示p为一个指针变量,前面的*表示*p指向的是char *类型的数据。换一句话来说,如果引用*p,就是得到p所指向的值,如果拿上面的例子来说就是字符串"Hello"和"World"。

    我们可以通过下面的代码来实现:

    int main()
    {
    	char *arr[]={"Hello","World"};
    	char **p;
    	int i;
    	for(i=0; i<2; i++)
    	{
    		p=arr+i;
    		printf("%s ",*p);
    	}
    	return 0;
    }

     

     

     

    展开全文
  • 指向同一数组两个指针变量 相减 表示 相差元素个数,可以 比较 地址编号大小两个指针变量不能相加 指针变量加一个数表示 跳过(指针变量类型的字节数)*(这个数)的字节数 数组名[ 不越界的情况下可以为复负数 ],...
  • C语言一维数组

    千次阅读 多人点赞 2019-04-19 15:03:03
    `数组类型 数组名[数组大小] 例: int a[10]; 注:数组的大小不能为零 *数组的下标是从0开始的,* 数组的初始化: 相当于给数组赋值; 我们一般运用for循环来给数组进行赋值; int a[10]; int i; for(i...
  • c语言中的数组首元素的地址和数组的地址是两个不同的概念,比如一个数组, int a[]={10,100}, 数组名a代表数组首元素的地址,&a代表数组的地址,数组地址&a用数组首元素的地址来代替,因此printf("%d\n",a)...
  • 分析题目中说到的数组一样大,那么也就是说数组中元素的个数和类型相等,那么要达到交换这两个数组,仅需将其每一位对应的元素进行交换即可,这样也就达到了交换数组的效果。 第一步: 先构造一个能将两个数交换...
  • c语言 一维数组折半查找法Problem statement: Write a C program to find two ... 问题陈述:编写一个C程序以在一维数组中找到两个最小的元素。 Example: Type1: (all the elements are not same & no of e...
  • C语言中的数组

    2019-05-19 15:25:05
    C语言中,每个数组两个基本特性: 1.元素类型: 存储在数组元素中的数值类型。 2.数组大小: 数组所包含的元素个数。 当新建一个数组时,必须指明数组的元素类型和数组大小。 数组声明 int intArray[10];...
  • 给你两个 有序 整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个 有序 数组
  • C语言实现动态数组

    2020-02-07 18:33:11
    C语言中,定义一个数组,则是在计算机的内存中占据了一块固定的区域用于存储数据。 定义数组: int array[10] = { 0 }; 我们定义一个一般的一维数组往往有几个参数,以上述例子为例: 数组存储数据的类型 上为...
  • C语言变长数组

    千次阅读 2014-03-17 18:47:28
    C语言变长数组之剖析 1、引言 我们知道,与C++等现代编程语言不同,传统上的C语言是不支持变长数组功能的,也就是说数组的长度是在 编译期就确定下来的,不能在运行期改变。不过,在C99标准中,新增的一项...
  • c语言数组数组相关的计算

    千次阅读 2016-11-18 08:30:41
    数组 1.数组的创建: 元素类型 数组名 [常量或者... 创建数组时,若不指定数组大小一定要初始化,但是初始化后可以不指定数组大小 注:(1)字符串放在数组里时,默认最后还有\0,字符数组则不是,有啥放啥  (2
  • c语言编程中我们这样定义数组时编译器回报一错误 const int n=5; int ar[n]; 这确实是一很棘手的问题,它会迫使我们用动态开辟空间的方法来定义数组,但是我们使用动态开辟的方法比较复杂,容易出现错误。...
  • C语言 创建动态数组种方法

    万次阅读 2019-02-27 15:13:02
    创建动态数组种方法 因本人才疏学浅,见识浅薄,有不当之处望指正,谢谢! 声明指针,调用malloc(), 将其...比如,种都可以在运行时确定大小数组: int a() { int n; int * pi; scanf(&quot;%...
  • C语言中字符数组越界会怎样?

    千次阅读 2020-04-14 17:39:27
    我们都知道C语言中的数组是不允许动态调整程度的,所有...事情是这样发生的,那是风和日丽的上午,我在家写代码,涉及到了C语言文件系统的内容,我用了一 fgets()函数,熟悉C语言的盆友都知道,这是一从指定的...
  • #C语言 一维数组的定义和引用 A. 一维数组的定义与初始化 1.几概念 数组名 数组长度 ... //数组大小为0没有意义 2.int b(2)(3); //不能使用圆括号 3.int k,a[k]; //不能用变量说明数组大小 4. int
  • 正在BASIC语言中,DIM A(N)语句可以定义一个包含N个元素(N是未知数)的数组,而在C语言中,定义数组时必须要...显然,这样做有两个缺点:一是若数组定义小于实际需要,将引起程序运行错误;二是若数组定义太大,又会造成内存空
  • 如何确定C语言数组大小

    千次阅读 2019-12-16 11:00:02
    如何确定C语言数组大小? 也就是说,数组可以容纳多少元素?
  • c语言指针与数组

    2015-12-24 10:34:13
    1、指针数组元素与指针在数组中,每一个数组元素在内存中都占用存储单元,都有相应的地址,数组元素指针就是数组元素的地址。 注:数组a不代表整个数组,只代表数组元素的地址。p=a表示把数组的首元素地址赋值给...
  • c语言变长数组

    千次阅读 2013-07-18 00:10:48
    C语言变长数组之剖析 (陈云川 ybc2084@163.comUESTC,CD) 1、引言 我们知道,与C++等现代编程语言不同,传统上的C语言是不支持变长数组功能的,也就是说数组的长度是在编译期就确定下来的,不能在运行期改变。...
  • C语言 矩阵/数组 马鞍点查找

    千次阅读 2020-05-05 21:46:37
    C语言 矩阵/数组 马鞍点查找 题目: 在矩阵中,一数在所在行中是最大值,在所在列中是最小值,则被称为鞍点。 编程思维 首先我们需要寻找行最大值,然后确定行最大值所在列。然后再所在列寻找最小值。根据判断次...
  • 有些人声称C语言没有多维数组,这是不对的。ANSI C标准在第6.5.4.2节以及第69号脚注上表示: 当几“[]”修饰符连续出现时(方括号里面是数组的范围),...尽管C语言数组数组当作是多维数组,但不能把几下标
  • C语言一维数组介绍

    2020-07-24 14:36:57
    数组就是一种容器(放东西的容器),可以想象成一柜子! 第一层就是首元素 array[0] 第二层就是第二元素 array[1] 第三层就是第三元素 array[2] … 第十层就是第十元素 array[9] 数组特点 1.其中所有的...
  • c语言-指针数组

    千次阅读 2018-11-10 22:53:05
    机器中有一些位置,每一位置被称为【字节】/byte,许多现代机器上,每字节包含8位。更大内存单位【字】,通常包含2或4字节组成。 一字包含4字节,它的地址是什么? 他仍然只有一地址,是最左边还是...
  • C语言的动态数组 VS C++的动态数组

    千次阅读 2018-08-26 19:37:09
    C语言中的动态数组 C语言中的动态数组就是动态内存分配的知识 首先,先看C语言的那些小秘密之动态数组https://blog.csdn.net/bigloomy/article/details/6615012,里面有关内存分配的内容,请看C语言的那些小秘密之...
  • C语言 指针与数组

    2019-03-27 22:04:38
    //指向数组的指针,内存长度是所有元素的和//区别于int *p2[5],这个是建立了一个数组,数组的元素全是指针 printf ( "\n%d,%d" , sizeof ( * p1 ) , sizeof ( * p2 ) ) ; //和sizeof(*a), sizeof(*(&a))等价...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 106,124
精华内容 42,449
关键字:

c语言两个数组比大小

c语言 订阅