sizeof与strlen的区别,以及不同元素存储的位置,以及bss段及rodata段

来源:互联网 发布:linux nginx 开机启动 编辑:程序博客网 时间:2024/04/30 11:39
这篇文章将会分为两大部分。第一部分主要讲述sizeof和strlen的区别,第二部分讲一下内存分配的位置以及相关的细节问题。
一、sizeof和strlen的区别。
有如下程序,输出是什么?
#include "stdio.h"
#include "string.h"
const char* a = "123456";
char b[]="123456";
char c[10]="123456";
int main(int argc, char *argv[]) {
    //check the function of sizeof and strlen
    printf("%d\t%d\t%d\n",sizeof(a),sizeof(b),sizeof(c));
    printf("%d\t%d\t%d\n",strlen(a),strlen(b),strlen(c));
    return 0;
}
答案是,4,7,10(sizeof输出),以及6,6,6(strlen输出)
why?我们不是常说,数组名称相当于一个常量指针么?怎么sizeof的结果不一样?
答案非常简单:“相当于”只是在某种条件下的一种近似,只是帮助我们理解的一种方法。实际上,指针就是指针,数组名称,还是数组名称。所以切记,在sizeof的时候,他们是不同的东西。原因是,对编译器而言,一个数组就是一个地址,一个指针就是一个地址的地址。
那么,有没有什么情况下他们一样呢?正如我们原来所接触到的一样,在很多情况下,他们可以被当做相同的东西。可以参考这篇文章:
http://bbs.chinaunix.net/thread-2295023-1-1.html

另外,数组b的sizeof结果是7,为什么呢?各位可以思考一下。

二、不同类型元素的存储位置
还是刚刚的那个例子,加一点东西:
#include "stdio.h"
#include "string.h"
char* a = "123456";
char b[]="123456";
char c[10]="123456";
char d[10];
int main(int argc, char *argv[]) {
    //check the function of sizeof and strlen
    printf("%d\t%d\t%d\n",sizeof(a),sizeof(b),sizeof(c));
    printf("%d\t%d\t%d\n",strlen(a),strlen(b),strlen(c));
    a[1]='a';
    b[1]='a';
    c[1]='a';
return 0;
}

于是系统就出错了:


这个就涉及到在内存中,各种变量(包括程序)都是怎么放置的了。
首先可以参考这篇文章:
http://blog.csdn.net/bingxx11/article/details/7796251

我们可以看到,从低到高依次是代码段,数据段,堆,空洞,栈(图中“堆栈段”实际应为“栈”)。里面的含义都很好理解,文章中也有讲到,就不多赘述了。通过文章我们可以看到,数据段中存储的都是(初始化过非0的)全局变量和静态变量(也就是常量)。
关于变量的位置问题,还有两篇文章值得参考,作者的实验精神非常强:
http://blog.csdn.net/lyh66/article/details/7515557
http://blog.csdn.net/lyh66/article/details/7519570
都值得一看。

三、关于bss段

只是,在“数据段”中,还专门指出了一个“bss”段,那么什么是bss段?这又是干什么的呢?
可以参考这篇文章:http://www.100ask.net/forum/showtopic-3717.aspx
摘抄并加上自己的一些解释,如下:

一般情况下,一个程序本质上都是由 bss段、data段、text段三个组成的。你从未见过有堆栈段吧——因为堆和栈都是属于运行时候的内存分配!data顾名思义,是全局的数据;text就是代码段(所以也叫文本段,text么)。而bss的名字叫做Block Started by Symbol segment,是指用来存放程序中未初始化的全局变量的一块内存区域,一般在初始化时bss 段部分将会清零(bss段属于静态内存分配,即程序一开始就将其清零了)。那么,在bss段中的(也就是刚刚程序中的数组d)和在data段的(刚刚程序中的数组a,b,c)
text和data段都在可执行文件中(在嵌入式系统里一般是固化在镜像文件中),由系统从可执行文件中加载;
而bss段不在可执行文件中,由系统初始化。(实际只是一个占位符,有人做过实验,参见
http://bbs.xml.org.cn/blog/more.asp?name=FoxWolf&id=29997)
换句话说,数组d虽然占了十个字节的空间,但是在生成的可执行文件中,并没有这十个字节,这样节约了硬盘的空间,可执行文件比较小(如果是嵌入式设备的话,当然更节约空间了)。当程序被加载运行的时候,操作系统会做一个初始化,将bss中占用的内存空间全都初始化为0。
不过这里有一个疑问,对于我们嵌入式设备来说,没有操作系统的话,如何去做这件事情呢?于是,继续百度之
http://blog.csdn.net/jinling1441/article/details/5430706
这篇文章告诉我们,实际上在单片机内部,有一系列“自带的”初始化程序(该文中为avr-glibc),它代替了PC中操作系统的角色,为我们的程序搭建了一个可以正常运行的环境。

四、关于rodata段
咦,那就奇怪了,我们的程序出错是在a[1]='a'这一行,既然我们都放在数据段了,应该是可以修改这里的数据的,相当于全局变量,但是为什么会报“内存不能为written”的错误呢?于是又百度了一下,可以参考这个网页:
http://wenwen.soso.com/z/q291937400.htm
简而言之,就是不同的编译器处理方式不一样,我刚巧用的是gcc,会把常量放在一个名为rodata的段中,这个段中的数据是不可以被修改的(映射为虚拟内存的时候,操作系统会对虚拟内存加一些特殊的机制)。
不过还有一个问题,这个神奇的.rodata段和.data段,有什么联系呢?到底是.data段的一部分,还是和.data段独立的呢?于是,又百度到了这个网页:
http://zhidao.baidu.com/question/359130406.html&__bd_tkn__=69f5465d3a2499274a6dab20fdb67eb3d55f9db9a879308703baf14879e8d7917a60cd30eeba896122ef7e11a1fbf820d0f226b722
通过作者的描述,我们可以知道,从内存的低地址到高地址,分别是.text,.rodata,.data,.bss段。也就是,.data段和.rodata是不同的。

呼呼,写到最后,我自己也不禁感慨:虽然看似是一个小小的问题,但是不断追问下去,还是可以学到不少东西的!最关键的是,里面70%的问题,我在面试的时候都被问过。。!!

 
原创粉丝点击