zoukankan      html  css  js  c++  java
  • char *c和char c[]区别

    char *c和char c[]区别

    问题引入:
    在实习过程中发现了一个以前一直默认的错误,同样char *c = "abc"和char c[]="abc",前者改变其内容程序是会崩溃的,而后者完全正确
    程序演示:

    #include <iostream>
    using namespace std;

    main()
    {
    char *c1 = "abc";
    char c2[] = "abc";
    char *c3 = ( char* )malloc(3);
    c3 = "abc";
    printf("%d %d %s ",&c1,c1,c1);
    printf("%d %d %s ",&c2,c2,c2);
    printf("%d %d %s ",&c3,c3,c3);
    getchar();
    }  

     

    测试环境Devc++
    代码
    运行结果
    2293628 4199056 abc
    2293624 2293624 abc
    2293620 4199056 abc

    参考资料:
    首先要搞清楚编译程序占用的内存的分区形式:
    一、预备知识—程序的内存分配
    一个由c/C++编译的程序占用的内存分为以下几个部分
    1、栈区(stack)—由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于

    数据结构中的栈。
    2、堆区(heap)—一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据

    结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
    3、全局区(静态区)(static)—全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态

    变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统

    释放。
    4、文字常量区—常量字符串就是放在这里的。程序结束后由系统释放。
    5、程序代码区
    这是一个前辈写的,非常详细
    //main.cpp
    int a=0;    //全局初始化区
    char *p1;   //全局未初始化区
    main()
    {
    int b;栈
    char s[]="abc";   //栈
    char *p2;         //栈

    static int c=0;   //全局(静态)初始化区
    p1 = (char*)malloc(10);
    p2 = (char*)malloc(20);   //分配得来得10和20字节的区域就在堆区。
    strcpy(p1,"123456");   //123456放在常量区,编译器可能会将它与p3所向"123456"优化成一个地方。
    }

    运行结果:

    abc

    如果改成char ptr[] = "abc"

    结果错误:

    chartest.c: In function ‘func’:
    chartest.c:6: warning: function returns address of local variable


    二、堆和栈的理论知识
    2.1申请方式
    stack:
    由系统自动分配。例如,声明在函数中一个局部变量int b;系统自动在栈中为b开辟空间
    heap:
    需要程序员自己申请,并指明大小,在c中malloc函数
    如p1=(char*)malloc(10);
    在C++中用new运算符
    如p2=(char*)malloc(10);
    但是注意p1、p2本身是在栈中的。
    2.2
    申请后系统的响应
    栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
    堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
    会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将

    该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大

    小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正

    好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
    2.3申请大小的限制
    栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地

    址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译

    时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间

    较小。
    堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地

    址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的

    虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
    2.4申请效率的比较:
    栈:由系统自动分配,速度较快。但程序员是无法控制的。
    堆:是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
    另外,在WINDOWS下,最好的方式是用Virtual Alloc分配内存,他不是在堆,也不是在栈,而是直接在进

    程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。
    2.5堆和栈中的存储内容
    栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的

    地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变

    量。注意静态变量是不入栈的
    当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主

    函数中的下一条指令,程序由该点继续运行。
    堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。
    2.6存取效率的比较
    char s1[]="aaaaaaaaaaaaaaa";
    char *s2="bbbbbbbbbbbbbbbbb";
    aaaaaaaaaaa是在运行时刻赋值的;
    而bbbbbbbbbbb是在编译时就确定的;
    但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
    比如:
    #include
    voidmain()
    {
    char a=1;
    char c[]="1234567890";
    char *p="1234567890";
    a = c[1];
    a = p[1];
    return;
    }
    对应的汇编代码
    10:a=c[1];
    004010678A4DF1movcl,byteptr[ebp-0Fh]
    0040106A884DFCmovbyteptr[ebp-4],cl
    11:a=p[1];
    0040106D8B55ECmovedx,dwordptr[ebp-14h]
    004010708A4201moval,byteptr[edx+1]
    004010738845FCmovbyteptr[ebp-4],al
    第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据

    edx读取字符,显然慢了。
    2.7小结:
    堆和栈的区别可以用如下的比喻来看出:
    使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会

    切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
    使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。

    自我总结:
    char *c1 = "abc";实际上先是在文字常量区分配了一块内存放"abc",然后在栈上分配一地址给c1并指向

    这块地址,然后改变常量"abc"自然会崩溃

    然而char c2[] = "abc",实际上abc分配内存的地方和上者并不一样,可以从
    4199056
    2293624 看出,完全是两块地方,推断4199056处于常量区,而2293624处于栈区

    2293628
    2293624
    2293620 这段输出看出三个指针分配的区域为栈区,而且是从高地址到低地址

    2293620 4199056 abc 看出编译器将c3优化指向常量区的"abc"

    继续思考:
    代码:
    输出:
    2293628 4199056 abc
    2293624 2293624 abc
    2293620 4012976 gbc
    写成注释那样,后面改动就会崩溃
    可见strcpy(c3,"abc");abc是另一块地方分配的,而且可以改变,和上面的参考文档说法有些不一定,

    #include <iostream>
    using namespace std;

    main()
    {
    char *c1 = "abc";
    char c2[] = "abc";
    char *c3 = ( char* )malloc(3);
    // *c3 = "abc" //error
    strcpy(c3,"abc");
    c3[0] = 'g';
    printf("%d %d %s ",&c1,c1,c1);
    printf("%d %d %s ",&c2,c2,c2);
    printf("%d %d %s ",&c3,c3,c3);
    getchar();
    }   

     

     

     

    C语言中char*和char[]用法区别分析

    这篇文章主要介绍了C语言中char*和char[]用法区别,包括使用过程中的误区及注意点分析,需要的朋友可以参考下

    char*与char[]的区别 
    编译器initialization工作代码分析cgcc
    在做项目的时候,要建立一个字符大小不定的变量;最开始用char 
    *param;然后在后面用时直接使用memcpy函数对其复制,结果导致错误;后来在网上搜了一下,在调用memcpy之前先用(char*)malloc()对其开辟内存,这样问题就解决了
    
    以下内容来至:http://topic.csdn.net/u/20110501/22/27989ea1-c79c-43bc-b11a-7e7dfb554881.html
    
    char 
    c[]="hello world"会为C开辟空间然后把hello word复制进去
    而char * c="hello 
    world"; 仅仅让c指向字符串,保存字符串的地址
    char c1[]="hello world";
    char c2[]="hello 
    world";
    c1和c2是开辟的两个不同的空间,彼此地址不同,分别把hello world 复制进去
    char * c1="hello 
    world";
    char * c2="hello world";
    时c1和c2的值相同,都存的是hello 
    world 这个字符串常量的首地址
    
    
    char * c="hello 
    world";
    把c隐式转换为const char*指针了
    是数组的话就不会了
    
    
    
    深入理解const char*p,char 
    const*p,char *const p,const char **p,char const**p,char *const*p,char**const 
    p
    一、可能的组合:
    (1)const char*p
    (2)char const*p
    (3)char *const p
    (4)const char 
    **p
    (5)char const**p
    (6)char *const *p
    (7)char **const p
    当然还有在(5)、(6)、(7)中再插入一个const的若干情况,不过分析了以上7中,其他的就可类推了!
    二、理解助记法宝:
    1。关键看const 修饰谁。
    2。由于没有 const *的运算,若出现 const * 的形式,则const实际上是修饰前面的。
    比如:char 
    const*p,由于没有const*运算,则const实际上是修饰前面的char,因此char const*p等价于const 
    char*p。也就是说上面7种情况中,(1)和(2)等价。 
    同理,(4)和(5)等价。在(6)中,由于没有const*运算,const实际上修饰的是前面的char*,但不能在定义时转换写成 const(char 
    *)*p,因为在定义是"()"是表示函数。
    三、深入理解7种组合
    (0)程序 
    在执行时为其开辟的空间皆在内存(RAM)中,而RAM里的内存单元是可读可写 的;指针只是用来指定或定位要操作的数据的工具,只是用来读写RAM里内存单元的工作指针 
    。若对指针不加任何限定,程序中一个指针可以指向RAM中的任意位置(除了系统敏感区,如操作系统内核所在区域)并对其指向的内存单元进行读和写操作(由RAM的可读可写属性决定);RAM里内存单元的可读可写属性不会因为对工作指针的限定而变化(见下面的第4点),而所有对指针的各种const限定说白了只是对该指针 的 读写权限 
    (包括读写位置)进行了限定 。
    
    (1)char 
    *p:p是一个工作指针,可以用来对任意位置 (非系统敏感区域)进 行读操作和写操作 
    ,一次读写一个字节(char占一个字节)。
    (2)const char*p或者char const 
    *p(因为没有const*p运算,因此const修饰的还是前面的char):可以对任意位置(非系统敏感区域)进行“只读” 操作。(“只读”是相对于char 
    *p来说所限定的内容)
    (3)char *const p(const 
    修饰的是p):只能对“某个固定的位置” 进 
    行读写操作,并且在定义p时就必须初始化(因为在后面不能执行“p=..”的操作,因此就不能在后面初始化,因此只能在定义时初始化)。(“某个固定的位 
    置”是相对于char *p来说所限定的内容)
    可以总结以上3点为:char *p中的指针p通常是”万能”的工作指针 
    ,而(2)和(3)只是在(1)的基础上加了些特定的限制 ,这些限制在程序中并不是必须的,只是为了防止程序员的粗心大意而产生事与愿违的错 
    误。
    另外,要明白“每块内存空间都可有名字;每块内存空间内容皆可变(除非有所限) ” 。比如函数里定义的char 
    s[]="hello";事实上在进程的栈内存里开辟了6个变量共6个字节的空间,其中6个字符变量的名字分别为:s1[0]、s1[1]、 
    s1[2]、s1[3]、s1[4]、s1[5](内容是'')
    {
    待验证 : 还有一个4字节的指针变量s 
    。不过s是“有所限制”的,属于char *const类型,也就是前面说的 (3)这种情况,s一直指向s[0], 
    即(*s==s[0]=='h'),可以通过*s='k'来改变s所指向的 s[0]的值,但不能执行(char *h=“aaa”;s=h;)来对s另外赋值。
    }
    (4)上面的(2)和(3)只是对p进行限定,没有也不能对p所指向的空间进行限定,对于"char 
    s[]="hello";const char*p=s;" 
    虽然不能通过*(p+i)='x'或者p[i]='x'来修改数组元素s[0]~s[4]的值,但可以通过*(s+i)='x'或者s[i]='x'来修改原数组元素的值--RAM里内存单元的可读可写属性不因对工作指针的限定而改变,而只会因对其本身的限定而改变。如const 
    char c=‘A’,c是RAM里一个内存单元(8字节)的名字,该内存单元的内容只可读,不可写。
    (5)const char **p或者char const**p 
    :涉及两个指针p和 *p。由于const修饰char ,对指针p没有任何限定,对指针*p进行了上面情况(2)的限定。
    (6)char *const *p:涉及两个指针p和 
    *p。由于const修饰前面的char*,也就是对p所指向的内容*p进行了限定(也属于前面的情况(2))。而对*p来说,由于不能通过"*p=..."来进行另外赋值,因此属于前面的情况(3)的限定。
    (7)char **const p : 涉及两个指针p和 
    *p,const修饰p,对p进行上面情况(3)的限定,而对*p,没有任何限定。
    四、关于char **p 、const char 
    **p的类型相容性问题
    1。问题
    char *p1;const *p2=p1;//合法
    char **p1;const 
    char**p2=p1;//不合法,会有警告warning: initialization from incompatible pointer type
    char **p1;char 
    const**p2=p1;//不合法,会有警告warning: initialization from incompatible pointer 
    type
    char**p1;char*const*p2=p1;//合法
    2。判断规则
    明确const修饰的对象!对于指针p1,和p2,若要使得p2=p1成立,则可读做 
    :
    “p1是指向X类型的指针,p2是指向“带有const限定”的X类型的指针 “。只要二者的X类型一样,就是合法的。
    char *p1;const 
    *p2=p1;//合法:p1是指向(char)类型的指针,p2是指向“带有const限定"的(char)类型的指针。
    char **p1;const 
    char**p2=p1;//不合法:p1是指向(char*)类型的指针,p2是指向 ((const char)*)类型的指针。
    char **p1;char 
    const**p2=p1;//不合法;与上等价。
    char**p1;char*const*p2=p1;//合法: 
    p1是指向(char *)类型的指针,p2是指向“带有const限定"的(char*)类型的指针。
    五、其他
    1。 含有const的单层或双层指针的统一读法:
    “p是一个指针,是一个[“带有const限定”的]指向[”带有const限定”的]X类型的指针”。
    l例如:const char* 
    const *p就是说:p是一个带有const限定的指向带有const限定的(char*)类型的指针。
    2。定义时const修饰的对象是确定的,但不能在定义时加括号,不然就和定义时用“()”表示的函数类型相混淆了!因此定义时不能写(char 
    *)const *p或者(const char) **p。
    六、问题探讨(由于博文后的留言有字符数目限制,将回复移到这里)
    问题1 (见博文后留言):讲解非常好,不过有个问题想探讨下: 例如: 
    const char wang[]={"wang"}; char *p; p=wang; 是错误的。 所以char *p中的P不能指向常变量。 
    (1)需要补充纠正。
    回复 : 你好!谢谢指正!我在ubuntu 10.04(gcc 
    4.4.3)下做了如下测试: 
    //test_const.c
    #include<stdio.h>
    int 
    main()
    {
    const char wang[]={"wang"};
    char 
    *p;
    p=wang;
    p[2]='c';
    printf("p is %s
    ",p);
    return 0;
    }
    编译 
    :
    gcc -o test_const 
    test_const.c
    输出如下 :
    test_const.c: In function ‘main’:
    test_const.c:17: 
    warning: assignment discards qualifiers from pointer target type
    执行:
    ./test_const
    p is wacg
    结论: 
    根据本博文中第四点--相容性问题,将const型的wang赋值给p是不合法的。但编译器对其的处理只是警告,因此执行时通过p修改了只读区域的数据。这应该是该编译器处理不严所致后果,不知你用的什么编译器?
    问题2 
    回答 http://www.linuxsir.org/bbs/showthread.php?t=239058 提到的问题
    在c语言里
    // test.c
    int main() {
    const 
    char* s1 = "test";
    char *s2 = s1;
    s2 = "It's 
    modified!";
    printf("%s
    ",s1);
    }
    out: It's modified!;
    这样也可以吗? 
    照我的理解岂不是const限定符在c语言里只是摆设一个?
    回复:
    (1)首先,以上代码编译时会出错warning: 
    initialization discards qualifiers from pointer target type,
    因为char *s2 = 
    s1和问题1提到的一样,不符合相容规则。
    
    
    (2)输出结果是正确的,代码分析如下:
    int main() {
    const char* s1 = 
    "test"; // 在只读数据区(objdump -h test后的.rodata区)开辟5字节存储"test",并用s1指向首字符‘t’。 
    char 
    *s2 = s1; // s2也指向只读数据区中“test”的首字符't'。 
    s2 = "It's modified!"; // 
    在只读数据区开辟15字节存储"It's modified!",并将s2由指向't'转而指向"It's 
    modified!"的首字符'I'。 
    printf("%s
    ",s1); // 从s1所指的‘t’开始输出字符串"test"。 
    }
    
    (3)总结:提问者的误区在于,误以为s2 = "It's 
    modified!"是对“test”所在区域的重新赋值,其实这里只是将“万能”工作指针s2指向另外一个新开辟的区域而已。比如若在char *s2 = 
    s1后再执行s2[2]='a'则是对“test”的区域进行了写操作,执行时会出现段错误。但这个段错误其实与const没有关系,因为“test”这块区域本身就是只读的。为了防止理解出错,凡事对于对指针的赋值(比如 
    s2 = "It's modified!" ),则将其读做:将s2指向“ It's modified! ”所在区域的首字符。
    
    
    (4)额外收获:执行gcc -o test 
    test.c后,“test”、“It's modified!”、"%s
    "都被作为字符串常量存储在二进制文件test的只读区
    域 
    (.rodata)。事实上,一个程序从编译到运行,对变量空间分配的情况如下:
    A。赋值了的全局变量或static变量=>放在可执行文件的.data段。
    B。未赋值的全局变量或static变量=>放在可执行文件的.bss段。
    C。代码中出现的字符串常量或加了const的A=>放在可执行文件的.rodata段。
    D。一般的局部变量=>在可执行文件中不占空间,在该二进制文件作为进程在内存中运行时才为它分配栈空间。
    E。代码中malloc或new出的变量=>在可执行文件中不占空间,在该二进制文件作为进程在内存中运行时才为它分配堆空间。
    问题3:(待进一步分析) 验证博文中 
    三(3)提到的是否为s分配空间,初步分析结果为:不分配!文中的s只是s[0]的地址的代号而已。
    #include<stdio.h>
    int 
    main() {
    int a=3;
    char s1[] = "test";
    int b=4;
    char s2[] 
    ="test2";
    printf("the address of a is %u
    ",&a);
    printf("s1 is 
    %u
    ",s1);
    printf("the address of s1 is %u
    ",&s1);
    printf("the 
    address of b is %u
    ",&b);
    printf("s2 is %u
    ",s2);
    printf("the 
    address of s2 is %u
    ",&s2);
    }
    输出结果:
    
    the address of a is 
    3213037836
    s1 is 3213037827
    the address of s1 is 3213037827
    the address 
    of b is 3213037832
    s2 is 3213037821
    the address of s2 is 3213037821
    
    由结果可以看出,编译器做了些优化。
    七、其他相关经典文章转载
    
    王海宁,华清远见嵌入式学院讲师,对const关键字的理解
    http://www.embedu.org/Column/Column311.htm
    
    目前在进行C语言补习时,发现很多的同学对于const这个关键字的理解存在很大的误解。现在总结下对这个关键字理解上的误区,希望在以后的编程中,能够灵活使用const这个关键字。
    1、 const修饰的变量是常量还是变量
    对于这个问题,很多同学认为const修饰的变量是不能改变,结果就误认为该变量变成了常量。那么对于const修饰的变量该如何理解那?
    下面我们来看一个例子:
    int main
    {
    char 
    buf[4];
    const int a = 0;
    a = 10;
    }
    这个比较容易理解,编译器直接报错,原因在于“a = 
    10;”这句话,对const修饰的变量,后面进行赋值操作。这好像说明了const修饰的变量是不能被修改的,那究竟是不是那,那么下面我们把这个例子修改下:
    int main
    {
    char 
    buf[4];
    const int a = 0;
    buf[4] = 97;
    printf(“the a is 
    %d
    ”,a);
    }
    其中最后一句printf的目的是看下变量a的值是否改变,根据const的理解,如果const修饰的是变量是不能被修改的话,那么a的值一定不会改变,肯定还是0。但是在实际运行的结果中,我们发现a的值已经变为97了。这说明const修饰的变量a,已经被我们程序修改了。
    那综合这两个例子,我们来分析下,对于第二例子,修改的原因是buf[4]的赋值操作,我们知道buf[4]这个变量已经造成了buf这个数组变量的越界访问。buf数组的成员本身只有0,1,2,3,那么buf[4]访问的是谁那,根据局部变量的地址分配,可以知道buf[4]的地址和int 
    a的地址是一样,那么buf[4]实际上就是访问了const int a;那么对buf[4]的修改,自然也修改了const int 
    a的空间,这也是为什么我们在最后打印a的值的时候看到了97这个结果。
    那么我们现在可以知道了,const修饰的变量是不具备不允许修改的特性的,那么对于第一个例子的现象我们又如何解释那。
    第一个例子,错误是在程序编译的时候给出的,注意这里,这个时候并没有生成可执行文件,说明const修饰的变量可否修改是由编译器来帮我们保护了。而第二个例子里,变量的修改是在可执行程序执行的时候修改的,说明a还是一个变量。
    综上所述,我们可以得出一个结论,那就是const修饰的变量,其实质是告诉程序员或编译器该变量为只读,如果程序员在程序中显示的修改一个只读变量,编译器会毫不留情的给出一个error。而对于由于像数组溢出,隐式修改等程序不规范书写造成的运行过程中的修改,编译器是无能为力的,也说明const修饰的变量仍然是具备变量属性的。
    2、 被const修饰的变量,会被操作系统保护,防止修改
    如果对于第一个问题,有了理解的话,那么这个问题,就非常容易知道答案了。Const修饰的变量是不会被操作系统保护的。
    其原因是操作系统只保护常量,而不会保护变量的读写。那么什么是常量?比如“hello 
    world”这个字符串就是被称为字符串常量。
    对于这个问题的另一种证明方法,可以看下面这个程序:
    int main
    {
    const int 
    a;
    char *buf = “hello world”;
    printf(“the &a is %p, the buf 
    is %p
    ”,&a, buf);
    }
    可以发现buf保存的地址是在0x08048000这个地址附近的,而a的地址是在0xbf000000这个地址附近的,而0x08048000附近的地址在我们linux操作系统上是代码段。这也说明了常量和变量是存放在不同区域的,自然操作系统是会保护常量的。
    如果我们知道这个道理后,再看下面的题目:
    int main
    {
    char *buf = 
    “hello”;
    buf[0] = ‘a’;
    printf(“the buf 
    is %s
    ”,buf);
    }
    我们可以思考下,这个程序的运行结果会是什么呢?
    
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////2012-7-11再次编辑
    问题引入:
    在实习过程中发现了一个以前一直默认的错误,同样char 
    *c = "abc"和char c[]="abc",前者改变其内
    容程序是会崩溃的,而后者完全正确。
    程序演示:
    测试环境Devc++
    代码
    #include 
    <iostream>
    using namespace std;
    main()
    {
        char *c1 = "abc";
        char c2[] = "abc";
        char *c3 = ( char* 
    )malloc(3);
        c3 = "abc";
        printf("%d %d %s
    ",&c1,c1,c1);
        printf("%d %d %s
    ",&c2,c2,c2);
        printf("%d %d 
    %s
    ",&c3,c3,c3);
        getchar();
    }   
    运行结果
    2293628 4199056 
    abc
    2293624 2293624 abc
    2293620 4199056 abc
    参考资料:
    首先要搞清楚编译程序占用的内存的分区形式:
    一、预备知识—程序的内存分配
    一个由c/C++编译的程序占用的内存分为以下几个部分
    1、栈区(stack)—由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于
    数据结构中的栈。
    2、堆区(heap)—一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据
    结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
    3、全局区(静态区)(static)—全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态
    变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统
    释放。
    4、文字常量区—常量字符串就是放在这里的。程序结束后由系统释放。
    5、程序代码区
    这是一个前辈写的,非常详细
    //main.cpp
       int a=0;     //全局初始化区
       char *p1;    //全局未初始化区
       main()
       {
        int b;栈
        char s[]="abc";    //栈
        char *p2;          //栈
        char 
    *p3="123456";    //123456在常量区,p3在栈上。
        static int c=0;    
    //全局(静态)初始化区
        p1 = (char*)malloc(10);
        p2 = (char*)malloc(20);    
    //分配得来得10和20字节的区域就在堆区。
        strcpy(p1,"123456");    
    //123456放在常量区,编译器可能会将它与p3所向"123456"优化成一个
    地方。
    }
    二、堆和栈的理论知识
    2.1申请方式
    stack:
    由系统自动分配。例如,声明在函数中一个局部变量int 
    b;系统自动在栈中为b开辟空间
    heap:
    需要程序员自己申请,并指明大小,在c中malloc函数
    如p1=(char*)malloc(10);
    在C++中用new运算符
    如p2=(char*)malloc(10);
    但是注意p1、p2本身是在栈中的。
    2.2
    申请后系统的响应
    栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
    堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
    会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将
    该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大
    小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正
    好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
    2.3申请大小的限制
    栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地
    址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译
    时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间
    较小。
    堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地
    址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的
    虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
    2.4申请效率的比较:
    栈:由系统自动分配,速度较快。但程序员是无法控制的。
    堆:是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
    另外,在WINDOWS下,最好的方式是用Virtual 
    Alloc分配内存,他不是在堆,也不是在栈,而是直接在进
    程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。
    2.5堆和栈中的存储内容
    栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的
    地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变
    量。注意静态变量是不入栈的。
    当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主
    函数中的下一条指令,程序由该点继续运行。
    堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。
    2.6存取效率的比较
    char 
    s1[]="aaaaaaaaaaaaaaa";
    char 
    *s2="bbbbbbbbbbbbbbbbb";
    aaaaaaaaaaa是在运行时刻赋值的;
    而bbbbbbbbbbb是在编译时就确定的;
    但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
    比如:
    #include
    voidmain()
    {
    char 
    a=1;
    char c[]="1234567890";
    char *p="1234567890";
    a = c[1];
    a = 
    p[1];
    return;
    }
    对应的汇编代码
    10:a=c[1];
    004010678A4DF1movcl,byteptr[ebp-0Fh]
    0040106A884DFCmovbyteptr[ebp-4],cl
    11:a=p[1];
    0040106D8B55ECmovedx,dwordptr[ebp-14h]
    004010708A4201moval,byteptr[edx+1]
    004010738845FCmovbyteptr[ebp-4],al
    第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据
    edx读取字符,显然慢了。
    2.7小结:
    堆和栈的区别可以用如下的比喻来看出:
    使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会
    切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
    使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。
    自我总结:
    char 
    *c1 = "abc";实际上先是在文字常量区分配了一块内存放"abc",然后在栈上分配一地址给c1并指向
    这块地址,然后改变常量"abc"自然会崩溃
    然而char 
    c2[] = "abc",实际上abc分配内存的地方和上者并不一样,可以从
    4199056
    2293624 
    看出,完全是两块地方,推断4199056处于常量区,而2293624处于栈区
    2293628
    2293624
    2293620 
    这段输出看出三个指针分配的区域为栈区,而且是从高地址到低地址
    2293620 
    4199056 abc 看出编译器将c3优化指向常量区的"abc"
    
    继续思考:
    代码:
    #include 
    <iostream>
    using namespace std;
    main()
    {
        char *c1 = "abc";
        char c2[] = "abc";
        char *c3 = ( char* 
    )malloc(3);
        //   *c3 = "abc" //error
        strcpy(c3,"abc");
        c3[0] = 'g';
        printf("%d %d %s
    ",&c1,c1,c1);
        printf("%d %d 
    %s
    ",&c2,c2,c2);
        printf("%d %d %s
    ",&c3,c3,c3);
        getchar();
    }   
    输出:
    2293628 4199056 abc
    2293624 2293624 
    abc
    2293620 4012976 
    gbc
    写成注释那样,后面改动就会崩溃
    可见strcpy(c3,"abc");abc是另一块地方分配的,而且可以改变,和上面的参考文档说法有些不一定,
  • 相关阅读:
    编译安装mysql-5.6.36
    MYSQL数据库基础篇
    MYSQL数据库初学者必看
    Centos7下安装与卸载Jdk1.8
    Linux与Window之间的上传与下载
    MySQL主从搭建
    zabbix通过插件percona进行监控MySQL
    suse系统关闭防火墙
    编译安装zabbix3.0
    centos7安装tomcat
  • 原文地址:https://www.cnblogs.com/qmfsun/p/4884902.html
Copyright © 2011-2022 走看看