指针、数组、数组指针、函数指针

1.数组和指针的大小

对于指针,无论它指向何种类型(int、char或是结构体等类型),指针自身总是占用4个字节(32位的long型大小),因为指针和它指向的内容是分离开的,指针本身只保存实际内容的地址,因此其大小就是系统地址范围的大小。
对于数组,其本身就是用在存放实际内容的,因此它的大小就是这个数组实际需要的内存大小,例如数组 short score[6]; 就是定义了一个包含6个short元素的数组,其大小为6*sizeof(short)=12。这个大小通常是静态的,在编译阶段就可以计算出来(除动态数组)。

我们先自己思考下面程序的输出结果,然后看和实际运行结果是否符合:

int main()
{
    short * count;
    char * name_p;
    char name_str[36];
    int classes[12];
    char * array_of_ptr[8]; //指针数组
    char (* ptr_of_array)[36]; //数组指针
    char real_str[36];
    int i,j;

    int array_2d[3][4] = {
        {2, 4, 5, 8},
        {8, 6, 3, 7},
        {7, 1, 9, 2},
    };

    char * strarr[] = {
        "Xi'an",
        "Guangzhou",
        "Hefei",
    };

    char * cityname = "Guangzhou";

    /* test1: 指针和数组的size测试 */
    printf("sizeof(count) = %d, sizeof(name_p) = %d, "
        "sizeof(name_str) = %d, sizeof(classes) = %d, "
        "sizeof(array_of_ptr) = %d, sizeof(array_of_ptr[0]) = %d, "
        "sizeof(array_of_ptr[1]) = %d, sizeof(ptr_of_array) = %d\n",
        sizeof(count), sizeof(name_p),
        sizeof(name_str), sizeof(classes),
        sizeof(array_of_ptr), sizeof(array_of_ptr[0]),
        sizeof(array_of_ptr[1]), sizeof(ptr_of_array));

    /* test2: 给指针数组赋值 */
    name_p = (char *) malloc (36);
    if (!name_p)
        return 1;

    strncpy(name_p, "Li Lei.", 35);
    strncpy(name_str, "Han Meimei.", 35);

    array_of_ptr[0] = name_p;
    array_of_ptr[1] = name_str;

    printf("\n%s, %s\n", array_of_ptr[0], array_of_ptr[1]);

    /* test3: 指针内容的size测试 */
    printf("\nsizeof(*count) = %d, sizeof(*name_p) = %d, sizeof(*array_of_ptr) = %d, sizeof(*array_of_ptr[0]) = %d, "
        "sizeof(*array_of_ptr[1]) = %d, sizeof(*ptr_of_array) = %d\n",
        sizeof(*count), sizeof(*name_p), sizeof(*array_of_ptr), sizeof(*array_of_ptr[0]), sizeof(*array_of_ptr[1]), sizeof(*ptr_of_array));

    /* test4: 给数组指针赋值 */
    ptr_of_array = &real_str;
    strncpy(*ptr_of_array, "Li Lei.", 35);
    printf("\n%s, %d\n\n", *ptr_of_array, sizeof(**ptr_of_array));

    /* test5: 二维数组的元素地址 */
    for (i = 0; i < 3; i++)
        for (j = 0; j < 4; j++)
        printf("%x ", &array_2d[i][j]);
    printf("\n");

    /* test6: 指针数组的元素地址 */
    for (i = 0; i < 3; i++)
    {
        printf("strarr[%d] pointer addr addr %#x, str addr %#x, size = %d\n", i, &strarr[i], strarr[i], sizeof(*strarr[i]));
    }
    printf("strarr total size = %d\n", sizeof(strarr));
    printf("cityname addr = %#x\n", cityname);

    return 0;
}

其中,

test1考察了数组和指针大小的最简单情形。

test3则是计算指针指向的内容的大小,这就要看内容实际的数据类型了,因此sizeof(*name_p)就等于1,因为它指向的是char型的数组(的第一个元素)。
我们也知道(name_p + 1)实际上指向的是 (name_p的地址 + 1×sizeof(char)) 的位置,对于指针的++运算要注意这一点:每次加1是加了一个数据类型的大小,因此对于一个int型指针p来说,p+1实际上地址加了4。若不想受此限制我们可以将p强制转换成char型指针再做加法,或者直接转换成整数的加法,即 ((unsigned long)p + 1)。

test2和test4下面会讲到。

test5可以看出,二维数组的每个元素实际上是连续的,也就是说用一维数组 array_1d[12] 来取 array_2d[3][4] 的元素完全没有问题。

我们一开始就说指针实际上分为两部分:指针本身和它指向的内容,因此打印&name_p和name_p的地址是不同的。而对于数组,数组定义只是给这块内存起了个别名,因此打印&name_str和name_str的地址是相同的。对于test6,指针数组的每个元素都是指针,因此可以看到&strarr[i]和strarr[i]的地址是不同的。

上面程序的运行结果:

sizeof(count) = 4, sizeof(name_p) = 4, sizeof(name_str) = 36, sizeof(classes) =
48, sizeof(array_of_ptr) = 32, sizeof(array_of_ptr[0]) = 4, sizeof(array_of_ptr[
1]) = 4, sizeof(ptr_of_array) = 4

Li Lei., Han Meimei.

sizeof(*count) = 2, sizeof(*name_p) = 1, sizeof(*array_of_ptr) = 4, sizeof(*arra
y_of_ptr[0]) = 1, sizeof(*array_of_ptr[1]) = 1, sizeof(*ptr_of_array) = 36

Li Lei., 1

d8fcac d8fcb0 d8fcb4 d8fcb8 d8fcbc d8fcc0 d8fcc4 d8fcc8 d8fccc d8fcd0 d8fcd4 d8f
cd8
strarr[0] pointer addr addr 0xd8fc98, str addr 0xdb6a84, size = 1
strarr[1] pointer addr addr 0xd8fc9c, str addr 0xdb6a78, size = 1
strarr[2] pointer addr addr 0xd8fca0, str addr 0xdb6a70, size = 1
strarr total size = 12
cityname addr = 0xdb6a78

test2和test4涉及到指针数组和数组指针,我们下面单独讲一讲。

2.指针数组

顾名思义就是“指针的数组”,数组的每个元素都是一个指针,因此上面的 array_of_ptr[8] 就是定义了8个char型指针,它的size当然也就是 4 * 8 = 32。每个指针的使用方法就和普通指针一样,如上面的test2。

3.数组指针

就是“指向数组的指针”,也就是定义一个指针,这个指针指向一个数组的起始位置。上面的程序中,ptr_of_array就是一个数组指针,那么 *ptr_of_array 就是这个数组。注意,由于ptr_of_array本质上仍是一个指针,因此sizeof(ptr_of_array)=4。

上面最后一次打印中可以看到,sizeof(*ptr_of_array)是36,虽然这时ptr_of_array尚未指向任何实际的空间。实际上这也是数组指针的用途:指明类型和大小,那么这里ptr_of_array就指明了由它指代的数组的元素个数为36,每个元素都是char型。
数组指针用来明确数据类型,指针本身没什么意义,在使用时,要将其指向一个具体的、相同类型的实体,如上面test4。

数组指针还可用在二维数组的传参上,我们来看这个例子:

int manip_array(int (*ap)[4], int row)
{
    int i, j;

    for (i = 0; i < row; i++)
    {
        for (j = 0; j < 4; j++)
        {
            printf("%d ", ap[i][j]);
        }
        printf("\n");
    }
    return 0;
}

int main()
{
    int array_2d[3][4] = {
        {2, 4, 5, 8},
        {8, 6, 3, 7},
        {7, 1, 9, 2},
    };

    manip_array(array_2d, 3);
/*
运行结果:
Li Lei.

2 4 5 8
8 6 3 7
7 1 9 2
*/
    return 0;
}

可见,参数ap就协助指明了二维数组的列数。(传递二维数组也可以用一个一维数组的指针,但是要重新计算数组下标,例如这里的array_2d[1][0]就相当于array_2d[4]。不过这种方式的可读性不强。)

4.数组和指针做形参时的区别

在做函数形参时,数组就完全被当做指针来对待了,二者没有区别。例如下面的各种数组的传参,sizeof(arg)都是4:

void handle_str_1(char * arg)
{
    printf("sizeof arg = %d\n", sizeof(arg));
}

void handle_str_2(char arg[])
{
    printf("sizeof arg = %d\n", sizeof(arg));
}

void handle_str_3(char arg[16])
{
    printf("sizeof arg = %d\n", sizeof(arg));
}

void handle_str_4(int arg[3][4]) //注意这里和传入 char (*arg)[4] 完全相同
{
    printf("sizeof arg = %d, sizeof arg[0] = %d\n", sizeof(arg), sizeof(arg[0]));
}

void handle_str_5(char * arg[8])
{
    printf("sizeof arg = %d\n", sizeof(arg));
}

void handle_str_6(char (* arg)[6])
{
    printf("sizeof arg = %d\n", sizeof(arg));
}

int main_array_pointer()
{
    char str[16];
    char * strp = "hello world!";

    char * array_of_ptr[8];
    char (* ptr_of_array)[6];
    char real_str[6];

    int array_2d[3][4] = {
        {2, 4, 5, 8},
        {8, 6, 3, 7},
        {7, 1, 9, 2},
    };

    int (* ptr_of_nums)[4] = array_2d; //指向二维数组

    /* sizeof的题目再复习一下~ */
    printf("sizeof(str) = %d, sizeof(strp) = %d, sizeof(array_2d) = %d, "
        "sizeof(array_2d[0]) = %d\n\n",
        sizeof(str), sizeof(strp), sizeof(array_2d), sizeof(array_2d[0]));

    printf("addr of &str = %#x, str = %#x, str + 1 = %#x\n", &str, str, str + 1);
    printf("addr of &strp = %#x, strp = %#x, strp + 1 = %#x\n", &strp, strp, strp + 1);
    printf("addr of array_2d = %#x, &array_2d = %#x, array_2d[0] = %#x, array_2d + 1 = %#x, *(array_2d + 1) + 1 = %#x\n\n",
        array_2d, &array_2d, array_2d[0], array_2d + 1, *(array_2d + 1) + 1);

    printf("%d\n", array_2d[1][1]);
    printf("%d\n", ptr_of_nums[1][1]);
    printf("%d\n", (*(ptr_of_nums + 1))[1]);
    printf("%d\n\n", (*(array_2d + 1))[1]);

    /* 传参 */
    handle_str_1(str);
    handle_str_1(strp);

    handle_str_2(str);
    handle_str_2(strp);

    handle_str_3(str);
    handle_str_3(strp);

    handle_str_4(array_2d);

    handle_str_5(array_of_ptr);

    ptr_of_array = &real_str;
    handle_str_6(ptr_of_array);

    return 0;
}

运行结果:

sizeof(str) = 16, sizeof(strp) = 4, sizeof(array_2d) = 48, sizeof(array_2d[0]) =
 16

addr of &str = 0x30fac8, str = 0x30fac8, str + 1 = 0x30fac9
addr of &strp = 0x30fabc, strp = 0xc76874, strp + 1 = 0xc76875
addr of array_2d = 0x30fa40, &array_2d = 0x30fa40, array_2d[0] = 0x30fa40, array
_2d + 1 = 0x30fa50, *(array_2d + 1) + 1 = 0x30fa54

6
6
6
6

sizeof arg = 4
sizeof arg = 4
sizeof arg = 4
sizeof arg = 4
sizeof arg = 4
sizeof arg = 4
sizeof arg = 4, sizeof arg[0] = 16
sizeof arg = 4
sizeof arg = 4

你就可以看到数组指针和二维数组的关系,我们也知道了为什么定义二维数组时 a[][4] 这样写是OK的,我们不必知道数组的行数,但必须知道列数,因为它就相当于 (*a)[4] ,而 a+1就得到a[1][0]的地址,a+2就得到a[2][0]的地址

5.函数指针

延伸开来,我们可联想到函数指针,也具有相同的用途,例如:

int (*pf) (int x); //声明一个函数指针

就定义了一个指针变量pf,它的类型是带一个int型参数、返回值是整型的函数。

同样的,fp要指向一个具体的函数才有意义:

pf = func; //给fp赋值

这样,调用fp()和调用func()的效果一样, y = fp(x) 。

我们还可以借助typedef来定义一个函数指针的类型,我们应该还记得typedef一个结构体类型的做法:

typedef struct _sd_buf {
    char * name;
    int sd_no;
} sd_buf;

这样的话,在定义结构体实例时,就可以写成 sd_buf sb; 而不必写成 struct _sd_buf sb;
那么,对于函数指针,我也可以这样做:

typedef int (*my_pf)(int x);

这里 my_pf 就是一个新的类型,可以简化代码,看下面这段代码就知道了:

int chn0_send_frame(int fd, char * frame_data)
{
    //send hd frame data to fd.
    printf("chn 0 sending frame...\n");
    return 0;
}

int chn1_send_frame(int fd, char * frame_data)
{
    //send hd frame data to fd.
    printf("chn1 sending frame...\n");
    return 0;
}

typedef int (*frame_handler)(int fd, char * frame_data); //定义一个函数指针类型frame_handler

int (*vga_send_frame)(int fd, char * frame_data); //直接定义一个函数指针变量

struct frame_policy {
    int chn;
    int (*send_frame)(int fd, char * frame_data); //不使用typedef定义成员
};

//不使用typedef定义形参
int register_frame_hander(struct frame_policy * fp, int (*send_frame)(int fd, char * frame_data))
{
    fp->send_frame = send_frame;
    return 0;
}

//使用typedef定义形参
int register_frame_hander_01(struct frame_policy * fp, frame_handler send_frame)
{
    fp->send_frame = send_frame;
    return 0;
}

int main_func_pointer()
{
    frame_handler fhandler;
    struct frame_policy chn0_policy;

    /* 给函数指针赋值的几种方式 */
    fhandler = chn0_send_frame;
    vga_send_frame = chn0_send_frame;
    chn0_policy.send_frame = chn0_send_frame;

    /* 调用函数指针 */
    fhandler(6, NULL);
    vga_send_frame(6, NULL);
    chn0_policy.send_frame(6, NULL);

    /* 参数为函数指针时,传参的方式 */
    //register_frame_hander(&chn0_policy, chn1_send_frame); //OK
    register_frame_hander_01(&chn0_policy, chn1_send_frame); //OK
    chn0_policy.send_frame(6, NULL);

    return 0;
}

可以看到,定义一个函数指针类型,简化了代码的编写,也增强了可读性。

PS: 对应的,“指针函数”就是一个返回值是指针类型的函数,没什么特别的,例如 int * func(int x);

6.给数组和指针赋值

在修改数组或指针的内容时,要注意是否允许修改,例如下面这段代码中的几种情况:

int main()
{
    char * strp = "abcdef";
    char strarr[] = "abcdef";

    char strarrb[] = "xyeigh";
    char * const strpconst = strarrb;

    strarr[2] = 'm'; //OK,数组自身的内存是可读写的
    printf("strarr = %s, sizeof = %d\n", strarr, sizeof(strarr));

    //strp[3] = 'n'; //错误,指针指向的是一个常量,不能修改该常量的内存。
    strp = "hijklmno"; //OK,指针自身是可以修改的,这里指向了另一个地址。
    printf("strp = %s\n", strp);

    //strarr = strarrb; //错误,数组本身不能作为左值。
    strcpy(strarr, strarrb); //可以通过逐个元素赋值或strcpy等字符串操作函数修改字符数组内容。
    printf("strarr = %s\n", strarr);

    printf("strpconst = %s\n", strpconst);
    //strpconst = strarr; //指针常量只能在定义时赋值,后面就不能更改了。

    return 0;
}

上面strp指向的是字符串常量(存放在一块只读内存),给strp[3]赋值就是试图修改这块内存,因此是不允许的。当然,如果strp指向的是strarr,修改strp[3]就没问题,因为strarr是可读写的。

对于strp自身,它是一个指针变量,它指向的地址可以改变,即可以指向另外一块内存区域。

而对于指针常量(注意代码中const的位置),和其他类型的const常量一样,在定义之后指针本身也不能修改了,即不能改变它指向的地址。

更多数组和指针的区别,有一篇很好的文章: http://blog.chinaunix.net/uid-26983585-id-3336911.html ,看完这篇文章,上面一些执行结果会理解的更清楚。

7.零长数组

sizeof的补充:sizeof 是关键字不是函数,sizeof计算大小在编译阶段就可以完成(变长数组除外),也意味着sizeof(a)和sizeof a都是正确的写法。
其次,sizeof的计算结果跟字节对齐有关,例如对于结构体,默认情况下结构体的大小是其中最长的成员的整数倍。可以通过#pragma pack(n)或者__attribute((aligned (n))或者attribute(packed)来改变默认对齐方式。

struct header
{
    char a;
    long b[0];
};

struct header1
{
    char a;
    long b[1];
    short c;
};

struct header2
{
    char a;
    short c;
};

int main_size0()
{
    struct header aa;
    struct header1 bb;
    struct header2 cc;

    printf("sizeof aa = %d, sizeof bb = %d, sizeof cc = %d\n",
        sizeof(aa), sizeof(bb), sizeof(cc));

    return 0;
}

运行结果为:

sizeof aa = 4, sizeof bb = 12, sizeof cc = 4

这里我们看到struct header结构体里有一个b[0]成员,这是个0长数组,

struct header
{
    char a;
    long b[0]; //或写作 long b[];
};

我机器上long长度是4,那么sizeof(header) = 4。可见成员bb[0]虽然不占空间,但在计算整个结构体size的时候也发挥了作用,仍然是按照最长类型来对齐的。

使用0长数组可以方便数据的分配和传输,我们在一个结构体后面紧跟数据的情况下就会用0长数组:没有数据时不占空间,有数据时可以很方便地分配和访问数据。例如下面的例子:

struct conn_ext {
    unsigned char offset[CONN_EXT_NUM];
    unsigned char len;
    char data[0];
};

struct conn_ext * ct_ext =
    (struct conn_ext *) malloc (sizeof(struct conn_ext) + data_len);

可见,在分配内存的时候,结构体和数据是一起分配的,且地址连续。这样,不仅通过结构体实例访问数据就很方便,而且拷贝这个实例的过程也很方便,直接从结构体指针开始拷贝sizeof(struct conn_ext) + data_len的长度,拷贝一次即可完成。
但要注意一点,在这个例子中,我们看到data的起始地址可能不是word对齐,那么在不支持非对齐内存访问的系统上,就要小心,在计算size时可以通过诸如 ALIGN(sizeof(struct conn_ext), align) + data_len; 的方式,在访问data时同样要注意。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值