某天突然想了个关于结构体的问题,由于很久没有接触 C 语言,一开始并不知道其实这个问题本身就存在语法错误的问题,但正是因为对 C 知识的遗忘,才有了这次探索与巩固。

0x00 最初的问题

假设有如下结构体:

struct foo
{
    /* some other variables...*/
    struct foo bar;
};

那么我在定义一个 foo 变量的时候,

int main()
{
    struct foo hello;
    return 0;
}

会不会因为结构体里有一个循环引用而造成“内存爆炸”呢?

于是我试着用 Visual Studio 2015 去编译这一段代码,发现编译时就会报语法错误:

"bar"使用未定义的 struct"foo"

于是我改成了下面这样:

struct foo
{
    /* some other variables...*/
    struct foo *bar;
};

再次编译,一切顺利。(链表就是这样的结构)

0x01 新的问题 - 指定指针类型到底有什么意义?

既然直接在结构体里定义一个结构体本身这种类型的成员变量是会出现语法错误的,那么,为什么定义一个结构体本身这种类型的指针变量却不会报错?
天哪,真的忘了好多 C 的知识!在小伙伴的提示下,我想起了指针变量是用来存放地址的,它占用的内存是固定的。

所以在结构体里定义一个指针是合法的。

可是,问题又来了:定义结构体本身类型的变量会报未定义的错,可是为什么定义结构体本身类型的指针变量却不会报错?这个时候它不也应该是未定义的吗?那定义指针变量时指定指针类型还有什么意义?

0x02 动手实验 - 指针类型转换

带着上面的疑问,我写了下面一段代码:

#include <stdio.h>
int main()
{
    int *foo;
    char bar = 'a';
    foo = &bar;
    printf("%c\n", *foo);
    return 0;
}

程序确实是输出了 a。好像 int 类型的指针变量也可以指向 char 类型的变量啊?然后我又试了把 foo 定义为 void *long *float *,结果都一样,既然指针是指向地址的,而且指针变量占用的内存空间都是一样的,那么定义指针变量时指定类型到底有什么意义?

和小伙伴讨论了一会,突然有人提出了指针运算的地址间隔,不同类型的指针变量在运算时的偏移量(暂且这么称呼吧)是不同的,好像有点明白了!

0x03 新的实验 - 指针运算的地址间隔

于是我把代码修改了一下

#include <stdio.h>

int main()
{
    int * foo;
    char * bar;
    char str[9] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i' };
    foo = &str;
    bar = &str;
    printf("%c %c %c\n", *foo, *(foo + 1), *(foo + 2));
    printf("%c %c %c\n", *bar, *(bar + 1), *(bar + 2));
    system("pause");
    return 0;
}

输出结果如下:

a e i
a b c

可以看到 *(foo + 1) 是跳了 4 字节(sizeof(int) 的长度),原来指针变量的类型是用来指定指针运算时的地址间隔的。

那么问题又来了。。。

struct foo
{
    /* some other variables...*/
    struct foo *bar;
};

在结构体里定义 struct foo* 类型的变量时为什么不报错?这个时候 struct foo 不是未定义的吗?那定义指针变量时指定指针类型还有什么意义?
编译器在编译的时候不需要给指针变量指定运算的地址间隔吗?它到底是怎么处理指针的?

0x04 真相大白

带着这个疑问,我又对代码进行了改动

#include <stdio.h>

int main()
{
    void * p;
    char str[9] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i' };
    p = &str;
    printf("int *:  %c %c %c\n", *(int *)p, *((int *)p + 1), *((int *)p + 2) );
    printf("char *: %c %c %c\n", *(char *)p, *((char *)p + 1), *((char *)p + 2) );
    system("pause");
    return 0;
}

运行结果:

int *:  a e i
char *: a b c

等等!翻了一下书,发现 void * 好像有点特殊,于是我把 void * p; 这一行改成了 int * p;,编译运行,结果一样。
明白了,原来指针是在运行的时候才指定运算的地址间隔而不是在编译的时候就指定的。


以上,是我这次探索的过程以及最后的结论。由于本人水平有限,在探索中肯定存在有不合理的地方,结论也必然会存在不够严谨之处,还请多多指教!
一个简单的甚至是本身就有问题的问题居然可以延伸出这么深的问题,看来平时真的得多思考多动手啊!

标签: c, 指针, 结构体

已有 2 条评论

  1. cxD cxD

    这也c语言危险的地方,非常容易内存泄漏

  2. dean dean

    测试一下图像

添加新评论