一、基础研究
写一个c程序,打印int、long、double型变量所占的字节数、地址、各个字节的地址和内容。打印地址和内容比较好办,打印地址可以用取址符&,打印内容直接输出就行了,那么怎么打印所占的字节数呢?我们打印一个东西是输出它的值,而变量所占的字节数没有变量或指针来存储,我们可以得到变量所占字节数的方法是用运算符sizeof,首先写的程序如下:
运行结果如下:
发现有错误:1、打印出的地址是负数。2、打印出的c的值为0。
结果发现%d是打印有符号十进制整数,而地址是十六进制无符号整数,所以应该用%x说明符打印。而这里c的内容打印错误也是因为转换说明符写错了,打印double型的变量应该用%lf作为说明符。下面为修改后的程序和运行结果:
还有一点值得注意的是对取址符取出的地址要先进行强制类型转换如(int)&a再对地址进行操作(运算或打印),因为&a是变量a的地址,如果不进行类型转换,在计算时会按照地址的规则进行计算,比如double型变量c,(int)&c+2是在c的地址上再加2个字节,而&c+2是在c的地址上加2个c的长度也就是16个字节。另外在打印c是我使用的是%p说明符,结果发现打印的地址没有变化,只是打印出的地址是以大写字母表示的,而且%x打印0000、0008的地址只会显示0和8,而%p是按照地址的格式显示0000和0008。
我觉得在本程序中,sizeof是可以通过自己写的语句代替的,假如我们要打印一个不知道类型的变量a所占的字节数,我们可以在它后面加一个变量b,这样我们就知道a和b的首地址,二者相减就可以得出a所占的字节数。修改程序如下:
运行结果和上面的是相同的。
再看第二个问题,要求打印结构体变量所占的字节数、地址、各数据项地址、内容和各个字节的内容。程序如下:
运行结果:
在这里要获得每一个字节的内容,我是将地址转换成数字加上字节数,但是这样的话需要在计算后再将结果强制转换成地址形式并输出,所以要定义一个指针变量p用来存放计算结果并打印。
这里有一个问题:在最后输出数组name的每个字节的值,结果数组各项和数组每个字节输出的结果都是两个字节的数据,而不是一个字节的数据,这是为什么?但是对数组进行初始化后结果就是对的:
这是为什么呢,为什么不初始化数组显示每个字节的值是超过一个字节的呢?
同时我还注意到,这里数组name的数据类型是char型,那么它的地址加1就是加一个字节,与上面的方法理论上结果是相同的,我们试验一下,在程序后面加上如下代码:
结果是相同的:
但是这只能对char型数组用,因为只有char型数据按地址加1是增加1个字节的。
再来看下一个问题:写一个程序,反映参数的存储空间与局部变量的存储空间在函数运行后收回。在函数的生命周期结束后参数和局部变量的存储空间会被释放,我们在之前的研究中已经研究过,参数和局部变量的存储空间都是栈段,在函数返回时将栈顶指针sp复原
即将使用过的栈段空间释放,此时虽然这一段空间里的数据没有被覆盖,但是已经不被当前函数使用了,其他函数或程序随时可以使用覆盖,所以空间被释放了。那么怎么体现这个过程呢?我们可以定义全局指针变量,在函数里将它指向参数和局部变量的地址,用*p将参数和局部变量的地址打印出来,之后再使用函数进行一次传参以改变栈段里的值,然后在主函数里再打印一次*p,这时p还是原来的参数和局部变量的地址,如果空间没有回收,此时该地址处的值就不变,否则就说明空间已经被回收了。程序如下:
运行结果为:
两次的结果不一样,说明局部变量的存储空间在函数运行后收回,因为参数和局部变量存储的方式是一样的,所以参数的存储空间也是在函数运行后收回。
二、拓展研究
1、为什么不初始化数组显示每个字节的值是超过一个字节的呢?
2、Sizeof的使用方法。
答:根据有关资料,sizeof运算符以字节为单位返回其操作数的大小,操作数可以是一个具体的数据对象,如一个变量名,也可以是一个类型,如float,如果它是一个类型,操作数必须被括在圆括号里,如sizeof n和sizeof(float)。sizeof操作符不能用于函数类型,不完全类型或位字段。不完全类型指具有未知存储大小的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void类型等。sizeof操作符的结果类型是size_t,它在头文件中typedef为unsigned int类型,size_t是后者用typedef机制创建的别名。
当操作数是指针时,sizeof依赖于编译器。例如Microsoft C/C++7.0中,near类指针字节数为2,far、huge类指针字节数为4。一般Unix的指针字节数为4。
当操作数具有数组类型时,其结果是数组的总字节数。
联合类型操作数的sizeof是其最大字节成员的字节数。结构类型操作数的sizeof是这种类型对象的总字节数,包括任何垫补(就是为了内存对齐而补充的内存单元)在内。
如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小。
3、Sizeof的实现方式。
答:之前我以为sizeof是以库函数的形式实现的,但是发现它是一个运算符。函数与操作符的区别就在于,函数是程序运行是动态计算出一个对象或者类型所占的内存字节数,然而sizeof()操作符是在编译的时候就把字节数计算好,然后把这个字节数编译进行程序。它的实现是编译器的工作。这个数是一个常量。
三、研究总结
Sizeof也是由别人编写的实现计算变量长度的一个函数,与一般的函数不同的是它是要对地址进行操作来实现的,这体现了c语言的特点。当然我们也可以自己写代码来代替它的功能,但是这样程序不精简,占用的空间也更多。人类进化的重要标志就是会用工具来减少自己的工作量,也就是由底层向高层发展的过程,库函数、宏的出现也符合这一趋势。随着科技的发展,我们要做的工作越来越少,而机器和系统、软件却越来越丰富、智能,这条进化之路的最后会是人工智能出现,机器具有接近人的思维吗?我觉得是有可能实现的。