精华内容
下载资源
问答
  • 相对许多其他高级语言说,C语言对数组本身提供的支持并不太多,尤其是不支持动态的数组定义。 本文总结了几种常见的数组使用中的错误用法,并通过分析原理解释错误原因,从而为掌握正确的数组用法提供帮助。同时...

         数组一直是编程语言学习中需要重点掌握的部分,它是最基本的数据结构类型,是构成字符串及其他许多重要构造结构的基础。相对许多其他高级语言来说,C语言对数组本身提供的支持并不太多,尤其是不支持动态的数组定义

         本文总结了几种常见的数组使用中的错误用法,并通过分析原理解释错误原因,从而为掌握正确的数组用法提供帮助。同时提供了几个相对有代表性的数组的使用方式及源码,以方便学习。

    指引

    • 数组定义方法及使用示例
    • 常见数组定义错误
    • 数组原理及坑
    • 矩阵乘积计算
    • 杨辉三角
    • 数组宏

    数组定义方法

    在C语言中,数组按如下的方法定义:

     ① 类型 数组名[数组大小];
    
             int array[10];
             char str[15];
      ② 类型 数组名[]={元素1,元素2,元素3,...};1
    
             int a={1,2,3,4,5};
             char ch[]={'a','b','c'};
      ③ 类型 数组名[数组大小]={元素1,元素2,元素3,...};2
    
             float x[4]={1.5, 3.2, 6.0, 23};
             int y[5]={1,2,3,4};

    数组使用方法示例

    /*
         在下述代码代码片段中展示了几种常见的错误 
    */
    
    #define N 5
    
    int main(){
            /*错误形式 01: 声明数组时没有确定长度 */
        int array1[];
    
            /*错误形式 02:数组长度必须在进行编译初期就能确定,
            而不能是在运算处理中才能计算出来的结果,如宏定义 */
        int m=5;
        int array[m];
    
            /*错误形式 03: 先声明了数组,再赋值*/
        int array2[N];
        array2={1,2,3,4,5};
    
            /*错误形式 04: 赋值范围超出了开辟的空间*/
        int array3[N]={1,2,3,4,5,6};
    
            /*错误形式 05: 试图进行对数组的整体赋值*/
        int a[N]={1,2,3,4,5},b[N];
        b=a;
    
        return 0;
    }

    数组原理及“坑”

         1. 数组在定义的时候,在内存空间中为其开辟了一块连续的空间,其大小为单个数组元素类型所占大小 sizeof(type) 与元素个数N的积,即:

             size = sizeof(type) * N

        2. 同一数组内的数据元素必须一样,即即便如下述定义:

            float  x[]={1, 2, 3, 4.0, 3, 44, 27, 8};

    其结果也是使得所有的元素都被强制转换为float类型!

        3. 数组一旦定义之后,数组名就表示数组的第一个元素的地址,很大程度上等同于指向该数组的指针,即:

            x==&(x[0]);
    
            //以下是关于该向量名称经常等同于指针来使用的情况的说明
            float *p;
            p = x; //或者 p = (float*)malloc(4)
            memset(x, 10, 4);  //将 x[0]到 x[3] 共4个位置赋值为10
            memset(p, 10, 4);  //将 float类型的地址从p开始到相邻的共4个位置全部赋值为10,和上一句效果相同
            memset(x+2, 10, 4); //将 x[2]到 x[5] 共4个位置赋值为10
    
            //数组名和指针,在以下表达式中不一样:
            sizeof(x) //等于数组元素的个数,即8 ,其中x的定义是 float  x[]={1, 2, 3, 4.0, 3, 44, 27, 8};
            sizeof(p) //等于p的类型(float类型的指针)所占空间字节数,为4;
    
            //题外话:
            // 在32位系统中,所有类型的指针变量都存放的是该指针在内存中的地址,所以所占用的都是4个字节; 而64位系统中,则是8个字节!

    如果试图按照这样的方式进行赋值:

            int x[]={1,2,3,4,5,6},y[6];
            y=x;

    就会报错,因为这一句话相当于:

            &(y[0])=&(x[0]);

    错误原因在于,左边的表达式 &(y[0]) 是一个内存中既定的地址而非一个变量符号,它不可被用于左值!3故是一种错误。

         4.正是由于上述原因,因此如果按以下方式进行数组的声明4

            int x[5];
           //或者:
           #define N 5
           int x[N];

        则必须对元素分别进行赋值,且只能通过下标的方式进行5,如:

            int a[5];
            {
                a[i]=i;
            }   
            //当所有元素赋值都相同时,也可以用 memset(a, 100, 5)的方式,比较简便

         5.数组在进行函数调用时,传递给形参的值为该数组的名称 / 第一个元素的地址,因此不是值传递,在被调用函数中对数组的处理,都会导致原数组的值被改变,这一点尤其需要注意。如:

            f(int x[])   // 定义函数f()
            {
                x[2] = -1;
            } 
    
            int array[10] = {0};
            f(array);    // 调用 f()处理该向量
            array[2] = ? // 结果是 -1, 这就是地址传递调用函数时产生的所谓的 “副作用”
    
            //  你肯定接触过的另一个例子是:
            //  排序函数,比如 sort(array),经过sort()排序函数处理后,array向量中的各个元素被改写了

         6.数组在进行函数调用时,传递给形参的值为该数组的第一个元素的地址,在进行处理时计算机不知道该连续的数组地址到哪里结束,因此必须同时再传入一个指定的大小参数:length,见上例。

         7.二维数组的使用同一维数组完全类似,只不过每一个一维数组的元素又是一个数组而已;多维数组完全类似,只要细心处理,就不会出现错误。

    矩阵计算C语言源码:

    #include<stdio.h>
    #include<stdlib.h>
    #include<time.h>
    
    void showYanghui(int );
    
    #ifndef MM
    #define MM 5
    #endif
    #ifndef NN
    #define NN 5
    #endif
    #ifndef KK
    #define KK 7
    #endif
    
    
    int main() {
        srand((unsigned)time(NULL));
        int a[MM][NN], b[NN][KK],C[MM][KK];
        for (int i = 0; i < MM; i++) {
            for (int j = 0; j < NN;j++) {
                a[i][j] = (int)((rand()-RAND_MAX/2)%9);
            }
        }
        for (int i = 0; i < NN; i++) {
            for (int j = 0; j < KK; j++) {
                b[i][j] = (int)((rand() - RAND_MAX / 2) % 9);
            }
        }
        /*显示a,b矩阵*/
        printf("matrix A=\n\n");
        for (int i = 0; i < MM; i++) {
            for (int j = 0; j < NN; j++) {
                printf("%3d\t",a[i][j]);
            }
            printf("\n");
        }
        printf("\n"); printf("\n");
        printf("matirx B=\n\n");
        for (int i = 0; i < NN; i++) {
            for (int j = 0; j < KK; j++) {
                printf("%3d\t", b[i][j]);
            }
            printf("\n");
        }
    
        /*矩阵求积*/
        for (int i = 0; i < MM; i++) {
            for (int j = 0; j < KK; j++) {
                int sum = 0;
                for (int k = 0; k < NN; k++) {
                    sum += a[i][k] * b[k][j];
                }
                C[i][j] = sum;
            }
        }printf("\n"); printf("\n");
    
    
        printf("C=A*B=\n\n");
        for (int i = 0; i < MM; i++) {
            for (int j = 0; j < KK; j++) {
                printf("%3d\t",C[i][j]);
            }
            printf("\n");
        }
        getchar();
    }

         上述代码的运行结果为:
            矩阵计算示例

         8.杨辉三角的完整实现:

    #include<stdio.h>
    #include<stdlib.h>
    
    #define N 20
    void showYanghui(int );
    
    int main() {
        showYanghui(13);
        return 0;
    }
    
    /*打印杨辉三角*/
    void showYanghui(int n) {
        /*产生杨辉三角的存储矩阵*/
        int x[N][N];
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                if (j == 0 || i == j) {
                    x[i][j] = 1;
                }else {
                    x[i][j] = x[i - 1][j - 1] + x[i - 1][j];
                }
            }
        }
    
        /*按照杨辉三角的显示形式显示*/
        for (int i = 0; i < n; i++) {
            for (int k = 0; k <( n-(i+1)); k++) {
                printf("  ");
            }
            for (int j = 0; j <= i; j++) {
                if (x[i][j] < 10) {
                    printf("%4d", x[i][j]);
                }else if (x[i][j] < 100) {
                    printf("%4d", x[i][j]);
                }else {
                    printf("%4d", x[i][j]);
                }
            }
            printf("\n");
        }
    }
    

         上述代码的运行结果为:
            产生的13行杨辉三角

    在宏定义中使用数组

         C语言中定义函数宏,实际上是在编译预处理时在被调用处将宏展开。通过宏定义的形式大大减少了工作量,而且有时候会使得程序运行时栈空间深度更浅,直接贴代码如下

    #include<stdio.h>
    #include<stdlib.h>
    
    #define SHOW_ARRAY(array){\
                int lengthOfArray=sizeof(array)/sizeof(array[0]);\
                for(int i=0;i<lengthOfArray;i++){\
                    printf("%d\t",array[i]);\
                    if((i+1)%5==0){\
                        printf("\n");\
                    }\
                }\
                printf("\n");\
    }
    
    #define DISPLAY_ARRAY(str,array){\
                int lengthOfArray=sizeof(array)/sizeof(array[0]);\
                for(int i=0;i<lengthOfArray;i++){\
                    printf("%s[%2d]=%d\t",str,i,array[i]);\
                    if(i!=0&&(i+1)%4==0){\
                        printf("\n");\
                    }\
                }\
                printf("\n");\
    }
    
    int main() {
        int vectory[] = { 53,78,35,89,23,62,22,12,62,95,27,28,8,86,32 };
        DISPLAY_ARRAY("vectory",vectory);
        printf("\n");
        SHOW_ARRAY(vectory);
    
        return 0;
    }
    

         上述代码的运行结果为:
           宏定义的方法显示示例
         其中,自定义字段“vectory”是可以随意替换的,例如换成“arr”:
           示例




    注:


    1. 字符数组的定义 比较灵活且更加容易出错,后续专门整理.
    2. 在赋值前指定大小: 这种情况也是合法的,但是绝不可以超过定义的大小,见:错误示例代码中的错误形式:04.
    3. 左值/右值:在编程语言中处于赋值符号左右两边的变量值,要注意左值一般是要被赋值的。
    4. 这是C语言中唯一正确的不在定义时就赋值的数组定义方法!
    5. 也叫直接寻址或直接访问方式,在访问 a[k] 时计算机内部存储器中对PC指针进行了如下运算: pc = sizeof(type)*k + &(a[0])
    展开全文
  • 如何确定C语言中数组大小

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

    如何确定C语言中数组的大小?

    也就是说,数组可以容纳多少个元素?


    #1楼

    如果您要处理未作为参数接收的数组,则sizeof方法是正确的方法。 作为参数发送给函数的数组被视为指针,因此sizeof将返回指针的大小,而不是数组的大小。

    因此,内部函数无法使用此方法。 相反,请始终传递一个附加参数size_t size指示数组中元素的数量。

    测试:

    #include <stdio.h>
    #include <stdlib.h>
    
    void printSizeOf(int intArray[]);
    void printLength(int intArray[]);
    
    int main(int argc, char* argv[])
    {
        int array[] = { 0, 1, 2, 3, 4, 5, 6 };
    
        printf("sizeof of array: %d\n", (int) sizeof(array));
        printSizeOf(array);
    
        printf("Length of array: %d\n", (int)( sizeof(array) / sizeof(array[0]) ));
        printLength(array);
    }
    
    void printSizeOf(int intArray[])
    {
        printf("sizeof of parameter: %d\n", (int) sizeof(intArray));
    }
    
    void printLength(int intArray[])
    {
        printf("Length of parameter: %d\n", (int)( sizeof(intArray) / sizeof(intArray[0]) ));
    }
    

    输出(在64位Linux操作系统中):

    sizeof of array: 28
    sizeof of parameter: 8
    Length of array: 7
    Length of parameter: 2
    

    输出(在32位Windows操作系统中):

    sizeof of array: 28
    sizeof of parameter: 4
    Length of array: 7
    Length of parameter: 1
    

    #2楼

    您可以使用&运算符。 这是源代码:

    #include<stdio.h>
    #include<stdlib.h>
    int main(){
    
        int a[10];
    
        int *p; 
    
        printf("%p\n", (void *)a); 
        printf("%p\n", (void *)(&a+1));
        printf("---- diff----\n");
        printf("%zu\n", sizeof(a[0]));
        printf("The size of array a is %zu\n", ((char *)(&a+1)-(char *)a)/(sizeof(a[0])));
    
    
        return 0;
    };
    

    这是示例输出

    1549216672
    1549216712
    ---- diff----
    4
    The size of array a is 10
    

    #3楼

    如果您真的想通过数组来传递数据,那么我建议您实现一个结构,以存储指向您想要的数组类型的指针和一个表示数组大小的整数。 然后,您可以将其传递给函数。 只需将数组变量值(指向第一个元素的指针)分配给该指针。 然后,您可以转到Array.arr[i]以获取第i个元素,并使用Array.size来获取数组中的元素数。

    我为您提供了一些代码。 它不是很有用,但您可以使用更多功能进行扩展。 不过,老实说,如果您要这些,就应该停止使用C,并使用内置这些功能的另一种语言。

    /* Absolutely no one should use this...
       By the time you're done implementing it you'll wish you just passed around
       an array and size to your functions */
    /* This is a static implementation. You can get a dynamic implementation and 
       cut out the array in main by using the stdlib memory allocation methods,
       but it will work much slower since it will store your array on the heap */
    
    #include <stdio.h>
    #include <string.h>
    /*
    #include "MyTypeArray.h"
    */
    /* MyTypeArray.h 
    #ifndef MYTYPE_ARRAY
    #define MYTYPE_ARRAY
    */
    typedef struct MyType
    {
       int age;
       char name[20];
    } MyType;
    typedef struct MyTypeArray
    {
       int size;
       MyType *arr;
    } MyTypeArray;
    
    MyType new_MyType(int age, char *name);
    MyTypeArray newMyTypeArray(int size, MyType *first);
    /*
    #endif
    End MyTypeArray.h */
    
    /* MyTypeArray.c */
    MyType new_MyType(int age, char *name)
    {
       MyType d;
       d.age = age;
       strcpy(d.name, name);
       return d;
    }
    
    MyTypeArray new_MyTypeArray(int size, MyType *first)
    {
       MyTypeArray d;
       d.size = size;
       d.arr = first;
       return d;
    }
    /* End MyTypeArray.c */
    
    
    void print_MyType_names(MyTypeArray d)
    {
       int i;
       for (i = 0; i < d.size; i++)
       {
          printf("Name: %s, Age: %d\n", d.arr[i].name, d.arr[i].age);
       }
    }
    
    int main()
    {
       /* First create an array on the stack to store our elements in.
          Note we could create an empty array with a size instead and
          set the elements later. */
       MyType arr[] = {new_MyType(10, "Sam"), new_MyType(3, "Baxter")};
       /* Now create a "MyTypeArray" which will use the array we just
          created internally. Really it will just store the value of the pointer
          "arr". Here we are manually setting the size. You can use the sizeof
          trick here instead if you're sure it will work with your compiler. */
       MyTypeArray array = new_MyTypeArray(2, arr);
       /* MyTypeArray array = new_MyTypeArray(sizeof(arr)/sizeof(arr[0]), arr); */
       print_MyType_names(array);
       return 0;
    }
    

    #4楼

    sizeof技巧是我所知道的最好方法,在括号的使用上有一个很小但(对我来说,这是一个主要的挑战)。

    维基百科条目清楚地表明,C的sizeof不是函数。 它是一个运算符 。 因此,除非参数是类型名称,否则不需要在参数周围加上括号。 这很容易记住,因为它使参数看起来像强制转换表达式,该表达式也使用括号。

    因此:如果您具有以下条件:

    int myArray[10];
    

    您可以使用以下代码找到元素数量:

    size_t n = sizeof myArray / sizeof *myArray;
    

    对我来说,这比带括号的替代方法容易读得多。 我也赞成在该部门的右侧使用星号,因为它比索引更简洁。

    当然,这也都是编译时,因此无需担心划分会影响程序的性能。 因此,请尽可能使用此表格。

    最好是在有一个实际对象时在一个对象上使用sizeof,而不是在一个类型上,因为这样一来,您就不必担心会出错并指出错误的类型。

    例如,假设您有一个函数,例如通过网络将某些数据作为字节流输出。 让我们调用函数send() ,并使其以指向要发送的对象的指针以及对象中的字节数作为参数。 因此,原型变为:

    void send(const void *object, size_t size);
    

    然后,您需要发送一个整数,因此您将其编码如下:

    int foo = 4711;
    send(&foo, sizeof (int));
    

    现在,您通过在两个位置指定foo的类型,引入了一种微妙的脚步射击方法。 如果一个更改但另一个没有更改,则代码将中断。 因此,请始终这样做:

    send(&foo, sizeof foo);
    

    现在,您已受到保护。 当然,您可以重复变量的名称,但是如果更改它,则很有可能以编译器可以检测的方式破坏该变量。


    #5楼

    int size = (&arr)[1] - arr;
    

    查看此链接以获取解释


    #6楼

    如果知道数组的数据类型,则可以使用类似以下内容的方法:

    int arr[] = {23, 12, 423, 43, 21, 43, 65, 76, 22};
    
    int noofele = sizeof(arr)/sizeof(int);
    

    或者,如果您不知道数组的数据类型,则可以使用类似以下内容的方法:

    noofele = sizeof(arr)/sizeof(arr[0]);
    

    注意:只有在运行时未定义数组(例如malloc)并且未在函数中传递数组时,此功能才有效。 在这两种情况下, arr (数组名称)都是一个指针。


    #7楼

    每个人都使用的宏ARRAYELEMENTCOUNT(x)评估不正确 。 实际上,这只是一个敏感的问题,因为您不能拥有导致“数组”类型的表达式。

    /* Compile as: CL /P "macro.c" */
    # define ARRAYELEMENTCOUNT(x) (sizeof (x) / sizeof (x[0]))
    
    ARRAYELEMENTCOUNT(p + 1);
    

    实际评估为:

    (sizeof (p + 1) / sizeof (p + 1[0]));
    

    鉴于

    /* Compile as: CL /P "macro.c" */
    # define ARRAYELEMENTCOUNT(x) (sizeof (x) / sizeof (x)[0])
    
    ARRAYELEMENTCOUNT(p + 1);
    

    正确评估为:

    (sizeof (p + 1) / sizeof (p + 1)[0]);
    

    确实,这与数组的大小没有太大关系。 我刚刚注意到了很多错误,因为他们没有真正观察C预处理器的工作方式。 您总是包装宏参数,而不涉及表达式。


    这是对的; 我的例子很糟糕。 但这实际上正是应该发生的情况。 如前所述, p + 1最终将成为指针类型,并使整个宏无效(就像您试图在具有指针参数的函数中使用该宏一样)。

    归根结底,在这种特定情况下,错误并不重要(因此,我只是在浪费每个人的时间;烦死了!),因为您没有带有“数组”类型的表达式。 但实际上,我认为关于预处理程序评估的要点很微妙。


    #8楼

    C中的数组大小:

    int a[10];
    size_t size_of_array = sizeof(a);      // Size of array a
    int n = sizeof (a) / sizeof (a[0]);    // Number of elements in array a
    size_t size_of_element = sizeof(a[0]); // Size of each element in array a                                          
                                           // Size of each element = size of type
    

    #9楼

    最好的方法是例如在以下结构中保存此信息:

    typedef struct {
         int *array;
         int elements;
    } list_s;
    

    实现所有必要的功能,例如创建,销毁,检查相等性以及您需要的所有其他功能。 作为参数传递更容易。


    #10楼

    执行摘要:

    int a[17];
    size_t n = sizeof(a)/sizeof(a[0]);
    

    完整答案:

    要确定以字节为单位的数组大小,可以使用sizeof运算符:

    int a[17];
    size_t n = sizeof(a);
    

    在我的计算机上,整数是4个字节长,所以n是68。

    为了确定数组中元素的数量,我们可以将数组的总大小除以数组元素的大小。 您可以使用以下类型来执行此操作:

    int a[17];
    size_t n = sizeof(a) / sizeof(int);
    

    并得到正确的答案(68/4 = 17),但如果类型a改你将有一个讨厌的错误,如果你忘了改变sizeof(int)为好。

    因此,首选除数是sizeof(a[0]) ,即数组的第零个元素的大小。

    int a[17];
    size_t n = sizeof(a) / sizeof(a[0]);
    

    另一个优点是,您现在可以轻松地在宏中参数化数组名称并获得:

    #define NELEMS(x)  (sizeof(x) / sizeof((x)[0]))
    
    int a[17];
    size_t n = NELEMS(a);
    

    #11楼

    sizeof(array) / sizeof(array[0])
    

    #12楼

    值得注意的是, sizeof在处理已衰减为指针的数组值时无济于事:即使它指向数组的开头,但对于编译器,它与指向该数组中单个元素的指针相同数组。 指针不会“记住”用于初始化数组的其他任何信息。

    int a[10];
    int* p = a;
    
    assert(sizeof(a) / sizeof(a[0]) == 10);
    assert(sizeof(p) == sizeof(int*));
    assert(sizeof(*p) == sizeof(int));
    

    #13楼

    #define SIZE_OF_ARRAY(_array) (sizeof(_array) / sizeof(_array[0]))
    

    #14楼

    您可以使用sizeof运算符,但不适用于函数,因为它将获取指针的引用,您可以执行以下操作找到数组的长度:

    len = sizeof(arr)/sizeof(arr[0])
    

    最初在这里找到的代码: C程序查找数组中元素的数量


    #15楼

    int a[10];
    int size = (*(&a+1)-a) ;
    

    有关更多详细信息,请参见此处此处


    #16楼

    函数sizeof返回数组在内存中使用的字节数。 如果要计算数组中元素的数量,则应将该数字除以数组的sizeof变量类型。 假设int array[10]; ,如果您计算机中的变量类型整数为32位(或4字节),为了获取数组的大小,您应该执行以下操作:

    int array[10];
    int sizeOfArray = sizeof(array)/sizeof(int);
    

    #17楼

    最简单的答案:

    #include <stdio.h>
    
    int main(void) {
    
        int a[] = {2,3,4,5,4,5,6,78,9,91,435,4,5,76,7,34};//for Example only
        int size;
    
        size = sizeof(a)/sizeof(a[0]);//Method
    
        printf ("size = %d",size);
        return 0;
    }
    

    #18楼

    我建议不要使用sizeof (即使可以使用它)来获取数组的两个不同大小中的任何一个,无论是元素数还是字节数,这是我在这里显示的最后两种情况。 对于这两种大小,可以使用下面显示的宏使其更安全。 原因是使代码的意图明显地体现给维护者,乍一看, sizeof(ptr)sizeof(arr)有所不同(这种写法并不明显),因此对于每个阅读代码的人来说,错误都是显而易见的。



    有关此主题的重要错误: https : //lkml.org/lkml/2015/9/3/428

    我不同意Linus提供的解决方案,即永远不要将数组表示法用作函数的参数。

    我喜欢将数组表示法用作将指针用作数组的文档。 但是,这意味着需要采用一种万无一失的解决方案,以便不可能编写错误的代码。

    在数组中,我们可能需要知道三种大小:

    • 数组元素的大小
    • 数组中的元素数
    • 数组在内存中使用的字节大小

    数组元素的大小

    第一个非常简单,我们处理数组还是指针都没有关系,因为它是通过相同的方式完成的。

    用法示例:

    void foo(ptrdiff_t nmemb, int arr[static nmemb])
    {
            qsort(arr, nmemb, sizeof(arr[0]), cmp);
    }
    

    qsort()需要此值作为其第三个参数。


    对于其他两个大小(这是问题的主题),我们要确保正在处理数组,如果没有处理,则中断编译,因为如果处理指针,则会得到错误的值。 当编译中断时,我们将能够轻松地看到,我们并没有在处理数组,而是在使用指针,并且我们只需要使用存储变量大小的变量或宏编写代码即可。指针后面的数组。


    数组中的元素数

    这是最常见的一个,许多答案为您提供了典型的宏ARRAY_SIZE:

    #define ARRAY_SIZE(arr)     (sizeof(arr) / sizeof((arr)[0]))
    

    鉴于ARRAY_SIZE的结果通常与ptrdiff_t类型的带符号变量一起使用,最好定义此宏的带符号变量:

    #define ARRAY_SSIZE(arr)    ((ptrdiff_t)ARRAY_SIZE(arr))
    

    成员数超过PTRDIFF_MAX数组将为此宏的签名版本提供无效值,但是从读取C17 :: 6.5.6.9来看,类似的数组已经开始发挥作用。 在这些情况下,仅应使用ARRAY_SIZEsize_t

    当您将此宏应用于指针时,最新版本的编译器(例如GCC 8)会向您发出警告,因此它是安全的(还有其他方法可确保对较旧的编译器安全)。

    它通过将整个数组的字节大小除以每个元素的大小来工作。

    用法示例:

    void foo(ptrdiff_t nmemb)
    {
            char buf[nmemb];
    
            fgets(buf, ARRAY_SIZE(buf), stdin);
    }
    
    void bar(ptrdiff_t nmemb)
    {
            int arr[nmemb];
    
            for (ptrdiff_t i = 0; i < ARRAY_SSIZE(arr); i++)
                    arr[i] = i;
    }
    

    如果此函数未使用数组,而是将其用作参数,则先前的代码将无法编译,因此将不可能有错误(假设使用的是最新的编译器版本,或使用了其他技巧) ,我们需要将宏调用替换为以下值:

    void foo(ptrdiff_t nmemb, char buf[nmemb])
    {
    
            fgets(buf, nmemb, stdin);
    }
    
    void bar(ptrdiff_t nmemb, int arr[nmemb])
    {
    
            for (ptrdiff_t i = 0; i < nmemb; i++)
                    arr[i] = i;
    }
    

    数组在内存中使用的字节大小

    通常将ARRAY_SIZE用作前一种情况的解决方案,但这种情况很少被安全地编写,可能是因为它不太常见。

    获取此值的常用方法是使用sizeof(arr) 。 问题:与上一个相同; 如果您有一个指针而不是一个数组,您的程序将失败。

    该问题的解决方案涉及使用与以前相同的宏,我们知道这是安全的(如果将其应用于指针,它将破坏编译):

    #define ARRAY_BYTES(arr)        (sizeof((arr)[0]) * ARRAY_SIZE(arr))
    

    它的工作原理非常简单:它消除了ARRAY_SIZE所做的除法ARRAY_SIZE ,因此,在进行数学取消后,您最终只得到一个sizeof(arr) ,但又增加了ARRAY_SIZE构造的安全性。

    用法示例:

    void foo(ptrdiff_t nmemb)
    {
            int arr[nmemb];
    
            memset(arr, 0, ARRAY_BYTES(arr));
    }
    

    memset()需要此值作为其第三个参数。

    和以前一样,如果将数组作为参数(指针)接收,则它将无法编译,因此我们必须将宏调用替换为值:

    void foo(ptrdiff_t nmemb, int arr[nmemb])
    {
    
            memset(arr, 0, sizeof(arr[0]) * nmemb);
    }
    

    #19楼

    对于多维数组,这有点复杂。 人们通常会定义显式宏常量,即

    #define g_rgDialogRows   2
    #define g_rgDialogCols   7
    
    static char const* g_rgDialog[g_rgDialogRows][g_rgDialogCols] =
    {
        { " ",  " ",    " ",    " 494", " 210", " Generic Sample Dialog", " " },
        { " 1", " 330", " 174", " 88",  " ",    " OK",        " " },
    };
    

    但是这些常量也可以在编译时使用sizeof求值:

    #define rows_of_array(name)       \
        (sizeof(name   ) / sizeof(name[0][0]) / columns_of_array(name))
    #define columns_of_array(name)    \
        (sizeof(name[0]) / sizeof(name[0][0]))
    
    static char* g_rgDialog[][7] = { /* ... */ };
    
    assert(   rows_of_array(g_rgDialog) == 2);
    assert(columns_of_array(g_rgDialog) == 7);
    

    请注意,此代码可在C和C ++中使用。 对于二维以上的数组,请使用

    sizeof(name[0][0][0])
    sizeof(name[0][0][0][0])
    

    等等,是无限的。


    #20楼

    “您介绍了一种微妙的脚下射击方法”

    C个“本机”数组不存储其大小。 因此,建议将数组的长度保存在单独的变量/常量中,并在每次传递数组时将其传递,即:

    #define MY_ARRAY_LENGTH   15
    int myArray[MY_ARRAY_LENGTH];
    

    您应该始终避免使用本机数组(除非您不能这样做,否则请多加注意)。 如果您正在编写C ++,请使用STL的“向量”容器。 “与数组相比,它们提供了几乎相同的性能”,它们的作用要大得多!

    // vector is a template, the <int> means it is a vector of ints
    vector<int> numbers;  
    
    // push_back() puts a new value at the end (or back) of the vector
    for (int i = 0; i < 10; i++)
        numbers.push_back(i);
    
    // Determine the size of the array
    cout << numbers.size();
    

    请参阅: http//www.cplusplus.com/reference/stl/vector/

    展开全文
  • 小编在定义一个变量的时候,数据类型往往不会经过特别的考虑;在使用数组的时候,很多时候会忽略数组越界的问题,因为这个问题在C/C++编译器中是不进行检查的,越界不是编译、链接错误,运行时也未必会出错;但是...
     
    

        小编在定义一个变量的时候,数据类型往往不会经过特别的考虑;在使用数组的时候,很多时候会忽略数组越界的问题,因为这个问题在C/C++编译器中是不进行检查的,越界不是编译、链接错误,运行时也未必会出错;但是直到上面的“不小心”引发问题的时候就会让人手足无措,当检查出问题的时候才追悔莫及。

        如果是在visual C++里进行编程,出现上述的情况估计不是大问题,因为C++的调试工具使用非常方便,可以相对容易地发现问题。但是,往往小编遇到的问题是在嵌入式系统中运行的,这个和硬件相关,且调试工具不是那么方便,出现问题的时候就很头大了。以下举几个我曾经遇到的例子。

        (1)我在大二的时候做一个智能车的比赛,单片机从传感器采集数据利用自带的AD转化成数字量,存入16位的寄存器。考虑到节约内存,于是小编想将结果存储到unsigned short (16位)中,这个本来是没有多大问题。但是,小编还希望多次采集数据取平均值,当时是3次。为了防止溢出,每次将采集到的数据先除以3,再进行相加。后来,我改成了采集6次数据,但是除数忘了改,还是3。于是,悲剧就发生了:智能车上有两组传感器,在AD值比较大的位置,得到的数据溢出,比较小的地方正常,这样,整个系统当然不能正常工作。还好,当时是用飞思卡尔的codewarrior进行调试,BDM可以无干扰的观察在线数据,及时发现了溢出的问题。后来改成long型变量,就没有问题了,也不用考虑那么多。现在想想,真是不值得,那个芯片的RAM还是挺大的,何必为了一点内存自找这么多麻烦?不过定义大数组的时候变量的类型和数组长度应该坚持“够用就好”。
        (2)第二个例子问题相当严重!!!我整整被这个问题困扰了两个月!现在忽然有点恍然大悟的感觉,于是才下定决心写这篇文章。小编的一个项目使用AT91作为单片机,其中应用了uC/OS操作系统,使用AT91的串口接收数据,中断方式,以g_Frame1作为接受缓冲,接受数据时将数据从g_Frame1拷贝到g_Frame,他们的类型都是UARTFRAME_DATA,数据类型定义为:
    typedef struct
    {
        Uint8   byHead;
        Uint8   byLength;
        Uint8   byData[150];
    } UARTFRAME_DATA;
        程序运行通常没有问题,但是运行一段时间(通常是2小时或者更长时间,甚至长达4天才出现一次)之后会出现卡死的情况,且卡死得很诡异,主程序已经不能运行,但是中断却可以进入。于是我就认为是串口的问题,把串口的配置改来改去还是不行。后来认为是操作系统的问题,但是把串口接收程序去掉之后,操作系统运行很长时间也不会出现问题。再后来认为可能是中断和主程序同时访问一个变量出现问题,于是加入临界区,但是这个显然不会起什么作用,因为即使同时访问,应该只是数据错误,而不应该是卡死。在各种改啊改之后,每次都以为“啊,这次终于你妹的解决了这个问题!”的时候,总是过不了多久,就旧病复发了。每次都想说“去你妹的,你搞了我两个月了”的时候,又总是鼓起勇气再次调试。
        其实小编很早就发现了一个现象,就是如果发送的数据比较混乱,出现了错误,程序一两分钟就会卡死。但是没有把这个现象和问题出现的原因联系起来,终于一个偶然的机会……
        我以为是在处理接收到的报文(保存在g_Frame)的时候,由于第二个字节错误,于是产生了数组越界的情况,又因为使用了操作系统,所以在修改g_Frame的时候,把g_Frame以外的变量也修改了,其中可能有操作系统中的变量,于是导致操作系统崩溃。于是,我将g_Frame的数据长度加长到300,但是发现烧写程序失败了。于是小编开始使用我最惯用的伎俩——屏蔽法,把程序一段一段的注释掉,发现是TestSendUDP的问题,发现这个函数只能发送255个字符,多一个字符烧写就会失败,怪哉啊!——但是真相只有一个,并且已经离我不远了。让我们来看看TestSendUDP的定义,大家不要笑:
    void TestSendUDP(Uint32 dstIp, Uint16 dstPort,  char* ch, Uint16 len)
    {
    char   i;
       ……
    for(i=0;i<len;i++)
    {
    *(payload+i)=ch[i];
    }
    ……
    }

    まさか!?就是那个char让我自己坑了自己两个月!i的大小最大只有127而已,如果传入的len太大,那么,函数中的for就陷入死循环了。如果传入的数据全都正确自然没有问题,但是串口传输数据很多时候不可靠,万一出错,且恰好是byLength,变得很大,这样不就卡在这个函数中了。i++最多可以加到-1(0xff),这时和Uint len=255相等,于是退出,故最多可以发送255个字符。此处,我们还可以看到,这个编译器对符号不敏感,只是比较两个数值的十六进制数。不过,如果要求i>len的时候可能效果会不一样(这个我没有试过)。这里之所以会陷入死循环,是因为我是这么调用的:TestSendUDP(0x808003b9,0x0a0a,(char*)&g_Frame,g_Frame.byHead-1+8);这样,长度就有可能超过255。

        那时候年轻,不懂事,以至于犯了这种错误。现在就知道定义变量类型的时候需谨慎。虽然改好之后还没有确定是这个问题,但是估计八九不离十,但如果不是这个问题,也算是长了见识了。
       (3)宏定义的问题
        有些时候百思不得其解,那段程序分明是执行了,但是为什么会没有应有的现象?变量分明定义了,为什么编译器告诉你没有定义?答案可能是:它没有执行,没有定义,只是你被骗了。
    void USARTIni(UART_MODLE *UART_info)
    {

        (对USART进行初始化)
    }
        这个函数是执行了,并且使用SourceInSight可以看到定义:
    #define USART               USART0
        但是当调用串口发送函数的时候,在UART0相应的引脚上没有任何波形,反而在UART1相应的引脚上有波形,这是为何?那是因为:
    #if UART_PORT == UART0_PORT
    #define USART               USART0
    #else
    #define USART               USART1
    #endif
    且有:
    #define UART_PORT   UART1_PORT

        因此夹在宏定义之间的东西一定要仔细看。第二个情况类似。宏定义是预编译时候起作用的,相当于一些开关,告诉编译器是否编译某些内容,如何编译。




    展开全文
  • 固定大小数组

    千次阅读 2018-12-24 11:19:16
    作者:Russ Bishop,原文链接,原文日期...假设我们想要用 statfs() 方法来确定某个挂载点所对应的 BSD 设备名。例如挂载点 /Volumes/MyDisk 对应的 BSD 设备是 /dev/disk6s2。 struct statfs fsinfo; if (statfs(pa...

    作者:Russ Bishop,原文链接,原文日期:2018-10-30 译者:zhongWJ;校对:numbbbbbCee;定稿:Forelax

    假设我们想要用 statfs() 方法来确定某个挂载点所对应的 BSD 设备名。例如挂载点 /Volumes/MyDisk 对应的 BSD 设备是 /dev/disk6s2

    struct statfs fsinfo;
    if (statfs(path, &fsinfo) != 0) {
        //错误
    }
    复制代码

    同等的 Swift 代码如下,只不过多了个 POSIX 错误帮助方法:

    func posix_expects_zero<R: BinaryInteger>(_ f: @autoclosure () throws -> R) throws {
        let returncode = try f()
        if returncode != 0 {
            // 如果需要,请在此处替换为自定义的错误类型。
            // NSError 会自动帮我们通过错误码得到对应的 C 字符串错误消息。
            throw NSError(
                domain: NSPOSIXErrorDomain,
                code: numericCast(returncode),
                userInfo: nil)
        }
    }
    
    // 采用默认的空初始化方法。Swift 能推断出结构体类型,
    // 但为了表示得更清楚,这里显式指定类型
    var fsinfo: statfs = statfs()
    statfs(path, &fsinfo)
    
    复制代码

    C 的引入物

    statfs() 函数在 C 语言的定义是 int statfs(const char *path, struct statfs *info)statfs 结构体有多个字段,但我们只关注 mount-from-name

    struct statfs {
        //...
        char f_mntfromname[MAXPATHLEN];
        //...
    }
    复制代码

    在苹果平台上 MAXPATHLEN == PATH_MAX == 1024。

    如果你在代码里硬编码 1024 而不是用更合适的宏,小心我的鬼魂会缠着你和你的家族十二代哦。

    噢哦。一个固定大小的数组。当被引入 Swift 中时,它会被当做一个有 1024 个元素的元组:

    public var f_mntfromname: (Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8)
    复制代码

    这个类型不是很实用。那我们能不能做点什么呢?由于这篇博客的存在,你可能已经猜到答案是「」。

    C 字符串

    这个固定大小的数组包含了 char。由于文档未曾提及,所以我们并不知道是否通过空字符来表示数组的终止。令人恼火的是,文档暗示在 64 位系统上,f_fstypename 字段是由空字符终止,但对于 mount to/from 两个字段却只字未提。这两个字段是根据被定义为 PATH_MAXMAXPATHLEN 宏来定义的而不是直接根据 PATH_MAX 宏,而 PATH_MAX 宏通常暗示数组由空字符来表示终止。我们是不是应该从中得到一点启发呢?

    对于固定大小的数组,有一些 C API 仍然采用空终止符(所以真实的字符串长度最长可以是 sizeof(array) - 1),而另一些则乐意填充整个缓冲区(所以不以空字符结尾的字符串长度最长可以是 sizeof(array))。这就是那种有害的绊脚石,它让你的程序看起来运行正常并通过所有测试,然后碰到某些新的 FizzyWizz 硬盘系统会有的奇怪的边界情况时,当突然碰到一个刚好由 1024 个字符组成的名称特别长的 BSD 设备时,结果就是你的程序出现了「可利用内存损坏」错误。

    这些值很有可能是空字符终止的(也可能不是),但我会告诉你如何来处理这个问题,以便在两种情况下都适用。这意味着我们再也不需要考虑这个问题,从而降低大脑负荷。在其他场景复用这个代码的人也不需要再考虑这个问题了。既然有这么大的好处,何不马上开始?

    偏题了,让我们回到正题……

    部分解决方案

    首先我们需要计算字段的偏移量。用新的 MemoryLayout.offset 方法可以得到结果:MemoryLayout<statfs>.offset(of: \Darwin.statfs.f_mntfromname)!。由于结构体和函数名字相同,当我们构造关键路径时,需要提供完整的路径名(fully-qualified name),否则会得到「无法确定有歧义的引用路径」的错误。我们可以强制解包返回值因为我们知道关键路径有效并且字段有偏移量。

    将字段的内存布局偏移量加上 withUnsafePointer 指针就可以得到一个指向结构体字段起始内存的指针。我们可以通过这种方式创建一个字符串对象:

    return withUnsafePointer(to: fsinfo, { (ptr) -> String? in
        let offset = MemoryLayout<statfs>.offset(of: \Darwin.statfs.f_mntfromname)!
        let fieldPtr = (UnsafeRawPointer(ptr) + offset).assumingMemoryBound(to: UInt8.self)
        if fieldPtr[count - 1] != 0 {
            let data = Data(bytes: UnsafeRawPointer(fieldPtr), count: count)
            return String(data: data, encoding: .utf8)
        } else {
            return String(cString: fieldPtr)
        }
    })
    复制代码

    我们首先快速检查了缓冲区是否是空字符结尾。如果是,就采用 C 字符串这条捷径。反之,为了便于使用 String 的长度限制构造方法,我们创建了一个 Data 实例。看起来用 Data(bytesNoCopy:count:deallocator:) 也行,但 String(data:encoding:) 初始化方法并不保证拷贝 Data 底层的缓冲区,Data 的构造过程也同样如此。虽然这种情况极少见,我们还是谨慎为好。(假如目前的方法会导致性能问题,我可能会花时间调研其他方案。)

    有一种可能情况是,先写入由空字符终止的较短的字符串到缓冲区,剩余的部分则是被垃圾数据填充。由于 Swift 在初始化结构体时会强制清除内存,所以只有当内核拷贝垃圾数据到这块地址时,上述情况才可能发生。我们可以忽略这种情况,因为内核会尽量避免泄漏内核内存到用户空间,否则我们就只能用更耗时的方式了。(将这些字节转换为字符串的方式数不胜数,我这里就不一一列举了。)

    现在我们来实现 statfs 的扩展:

    extension statfs {
        var mntfromname: String? {
            mutating get {
                return withUnsafePointer(to: fsinfo, { (ptr) -> String? in
                    let offset = MemoryLayout<statfs>.offset(of: \Darwin.statfs.f_mntfromname)!
                    let fieldPtr = (UnsafeRawPointer(ptr) + offset).assumingMemoryBound(to: UInt8.self)
                    let count = Int(MAXPATHLEN)
                    if fieldPtr[count - 1] != 0 {
                        let data = Data(bytes: UnsafeRawPointer(fieldPtr), count: count)
                        return String(data: data, encoding: .utf8)
                    } else {
                        return String(cString: fieldPtr)
                    }
                })
            }
        }
    }
    复制代码

    这段代码很管用,但假如我们也想处理别的字段,例如 f_mntoname 呢?复制代码似乎不怎么好,所以让这段代码支持泛型,使之更加通用才对;我们只需要接受 key path 和 count 作为参数,再稍作修改就可以了:

    func fixedArrayToString<T>(t: T, keyPath: PartialKeyPath<T>, count: Int) -> String? {
        return withUnsafePointer(to: t) { (ptr) -> String? in
            let offset = MemoryLayout<T>.offset(of: keyPath)!
            let fieldPtr = (UnsafeRawPointer(ptr) + offset).assumingMemoryBound(to: UInt8.self)
            if fieldPtr[count - 1] != 0 {
                let data = Data(bytes: UnsafeRawPointer(fieldPtr), count: count)
                return String(data: data, encoding: .utf8)
            } else {
                return String(cString: fieldPtr)
            }
        }
    }
    
    extension statfs {
        var mntfromname: String? {
            get {
                return fixedArrayToString(
                    t: self,
                    keyPath: \Darwin.statfs.f_mntfromname,
                    count: Int(MAXPATHLEN))
            }
        }
    
        var mntonname: String? {
            get {
                return fixedArrayToString(
                    t: self,
                    keyPath: \Darwin.statfs.f_mntonname,
                    count: Int(MAXPATHLEN))
            }
        }
    }
    复制代码

    结语

    现在你知道怎么把有 N 个元素的元组变成更实用的东西了吧。

    本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权,最新文章请访问 swift.gg

    展开全文
  • 但是,每个系统指针类型的大小确定的,所以可以结构体指针或者函数指针作为结构体成员。 结构体字节对齐 结构体变量本身从4字节对齐的位置开始存放。 整个结构体所占空间还要是成员中‘占字节最大的...
  • 变量的意义就是确定目标并提供存放的空间。 变量的命名规则 1.变量名只能是英文字母(A-Z,a-z)和数字(0-9)或者下划线 _ 组成。 2.第一个字母必须是字母或者下划线开头。 3.变量名区分大小写。 4.不能使用关键字...
  • 问题描述: 通常我们会定义数组,如int a[100]; 然后再写别的代码,然后在...我觉得他可能是个数组,但不确定,而且我还想知道a这个数组大小。 通常我们会这么做sizeof(a)/sizeof(a[0]) 但是这样做有问题,如果a
  • C语言_宏定义

    万次阅读 多人点赞 2019-02-02 00:20:03
    C预处理器在源代码编译之前对其进行一些文本性质的操作,主要任务包括删除注释、插入被#include进来的文件内容、定义和替换由#define 定义的符号以及确定代码部分内容是否根据条件编译(#if )进行编译。...
  • 宏定义`define 用一个指定的标识符(即名字)代表一个字符串,它的一般形式为: `define标识符(宏名)字符串(宏内容) 如:`define signal string 它的作用是指定用标识符signal代替string这个字符串,在...
  • 可变大小数组

    千次阅读 2016-08-22 19:20:19
    定义数组的时候通常要先确定数组大小,但是在实际运用当中,我们往往不知道我们要用到的这个数组到底要多大,所以需要用到可变大的数组。//声明数组结构体 typedef struct { int *array; int size; }Array;//...
  • C语言- 一维数组定义及访问

    千次阅读 2019-03-19 09:17:20
    比较正式的定义是:数组是具有一定顺序关系的若干相同类型数据集合 一维数组定义: 基本格式:类型符 数组名[常量表达式] 类型符:表示基本数据类型,如int, float,double,char.. 常量表达式:表示数组元素...
  • 5.1数组定义&5.2数组的顺序表示和实现
  • 转自:...   阅读Chromium代码时,碰到一个arraysizeof,定义如下: template typename T, size_t N> char (&ArraySizeHelper(T (&array)[N]))[N]; // That gcc want
  • Excel VBA基础语法——数组(四)

    万次阅读 2018-08-06 14:20:11
    在声明变量的过程中会出现一次性需要声明多个同类型变量的情况,如果还是按照常规的方法,那么程序就比较杂乱,为了一次性声明多个同类型变量,引入了数组的概念。数组就是同类型多个变量的集合,也是变量。数组声明...
  • C++宏定义的优缺点

    千次阅读 2014-09-01 08:50:11
     #define是C语言中提供的宏定义命令,其主要目的是为程序员在编程时提供一定的方便,并能在一定程度上提高程序的运行效率,但学生在学习时往往不能 理解该命令的本质,总是在此处产生一些困惑,在编程时误用该命令...
  • 数组的认识

    2016-07-18 17:16:29
    一,数组 1,数组的定义  #define MAX_SIZE 1000  int a[MAX_SIZE];...使用宏定义来确定数组大小,易于移植 int a[]={1.2.3};此时数组大小是sizeof(a) = 12;   2,将数组初始化为0  a[MAX_SIZE...
  • C语言宏定义

    2021-02-20 12:09:22
    原文链接:... 一....  编译一个C语言程序的第一步骤就是预处理阶段,这一阶段就是发挥作用的阶段。C预处理器在源代码编译之前对其进行一些
  • 新的更新内容请到mwhls.top查看。...Mark Harrison 提问:我该如何确定C里面数组大小?即数组里面能容纳多少个元素?22 回答:Mark Harrison - 1394 位用户认为有用:摘要:int a[17];size_t n = siz
  • 记忆化搜索以及记忆数组大小的影响 一 问题的引入 小编在回顾欧拉计划时遇到记忆化搜索问题,略有感触,感觉有必要和大家分享。 话不多说咱们直接上截图: 问题大意: 一个数,若为偶数,则它下一次将变化为n / 2;...
  • 11.1 数组

    2020-11-20 09:08:43
    数组通常被用来储存程序需要的数据。例如,一个内含12个整数元素的数组可以储存12个月的天数。...代码中的PI已定义。C使用新的语法初始化数组,如下所示: int main(void) { int powers[8] = {1,2,4.
  • 数组

    2016-12-01 22:22:51
    数组是具有一定顺序关系的若干对象的集合体,组成数组的对象称为该数组的元素。...数组定义时,不能用另外一个已定义数组数组名进行初始化。 如: Int a[3]= {0}; Int b[3]= a; //error 数
  • c#宏定义

    千次阅读 2014-12-25 15:41:17
    转载:... ...预处理过程扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。...语言中,并没有任何内在的机制完成如下一些功能:在编译时包含其他源文件、定义宏、根据条件 决定编译
  • C++基础之数组

    2018-12-25 22:16:16
    也就是说,例如我们有5个整型数值需要存储,但我们不需要定义5个不同的变量名称,而是用一个数组(array)存储这5个不同的数值。注意数组中的元素必须是同一数据类型的,在这个例子中为整型(int)。 例如一个存储5...
  • 预处理器定义宏

    千次阅读 2018-08-25 07:50:56
    利用“条件编译”指令,您可以通过测试一个常量表达式或标识符取消对源文件的某些部分的编译,以确定将哪些文本块传递给编译器以及在预处理期间将从源文件中删除哪些文本块。 #define指令通常用于将有用标识符与...
  • 11. 宏定义和typedef区别?
  • 从源代码生成可执行文件的各个阶段为: C源程序(.c)->编译预处理(.i)->编译(.s)->优化程序->汇编程序(.o)->链接程序->可执行文件(.exe) ...预处理过程在编译时处理包含其他源文件、定义宏、根据条
  • 宏定义的妙用

    2015-11-07 21:32:47
    宏定义的妙用 #define ToChar(x) #@x //x加上'' #define Conn(x, y) x##y //连接x,y #define ToString(x) #x //x加上"" #include #include using namespace std; int main(){ /*Conn(x, y)*/ int iNum1 = ...
  • 预处理(pre-treatment),是指在进行最后加工完善以前进行的准备过程,具体应用在不同的行业或领域,会有不同的解释。...这个过程并不对程序的源代码进行解析,但它把源代码分割或处理成为特定的符号用来支持调用。
  • sizeof求struct、指针、数组大小

    千次阅读 2013-06-07 14:26:08
    这个也在stddef.h中定义,如下: #define offsetof(s,m) (size_t)&(((s *)0)->m) 例如,想要获得S2中c的偏移量,方法为 size_t pos = offsetof(S2, c);// pos等于4 由于结构体的成员可以是复合类型,比如...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 36,022
精华内容 14,408
关键字:

以宏定义来确定数组大小