zoukankan      html  css  js  c++  java
  • char *name 与 char name[]的区别

    VC中char *name 与 char name[]的区别(基础知识)

    要点char* name="abc"指的是常量字符串,不可以修改指针,是兼容老的写法;char[] name="abc"是指针,可以修改;

    在学习过程中发现了一个以前一直默认的错误,同样char *c = "abc"和char c[]="abc",前者改变其内容程序是会崩溃的,而后者完全正确。
    程序演示:
    测试环境Devc++
    代码 #include <stdio.h>
    #include <string.h>
    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 

    #include <stdio.h>
    #include <string.h>
      int a=0;    //全局初始化区
      char *p1;   //全局未初始化区
       main()
      ...{
       int b;栈
       char s[]="abc";   //栈
       char *p2;         //栈
       char *p3="123456";   //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小结:
    堆和栈的区别可以用如下的比喻来看出:
    使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
    使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。

    总结:

    1. char *c1 = "abc"; 
    2. char c2[] = "abcd"; 
    3. char *c3 = ( char* )malloc(4); 
    4. c3 = "abc" 
    5. strcpy(c3,"1234"); 
    6. c3[0] = 'g';


    分析: 
    1。上面代码中的 字符串常量 "abc","abcd","1234",都是存放在所谓的文字常量区; 
    2。c1,c2,c3 这个三变量,都存放在栈中

    3。在VC中测试,CPU4个字节对齐吧,EBP为栈顶指针

    c1 的地址,就是ebp - 04h,占用4个字节 
    c2 的地址,就是ebp - 0ch,占用8个字节 
    c3 的地址,就是ebp - 10h,占用4个字节

    4。存储内容比较 
    c1 的4个字节,保存是的字符串常量 "abc"的地址 
    c2 的8个字节,保存就是就"abcd\0"还有3个字节未用;它不保存字符串常量 "abcd"的地址,而是将内容复制过来

    c3和c1一样,也是保存一个地址,但这个地址,是在堆中,

    结论: 
    所谓c中char * 和 char []的区别

    char * 在栈中是4个字节的指针, 
    而 char []将在栈中申请合适的内存来保存初始化的数据,

    也就是说 
    char c2[]="abcd"; 和char c2[5]="abcd";一样的; 
    若char c2[n],则在栈中分配n个字节;

    所以c2[1]='0'是正确的,c1[1]='a'是错误的,因为字符串常量不允许修改;

    同时也说明了上面的代码 
    ... 
    char a=1; 
    char c[]="1234567890"; 
    char *p="1234567890"; 
    a = c[1]; 
    a = p[1]; 
    ...

    a = c[1];要比a = p[1];快的原因,少了一条指令嘛

     

    关于指针的论述
    1.指向常量的指针。

    char buf[ ]=“john”;

    const char *pbuf=buf;(可以认为这个const修饰的是cahr,所以char类型是常量)

    即 如果想要这么做,*(pbuf+I) = ' a'; //错误

    但这样做可以,char buf2[ ] = “nike”;

    pbuf = buf2; //正确。(其实这里隐式的把buf2转成了 const char*)

    2.指针常量(指针本身是常量)

    char buf[] = "abc";

    char *const pbuf = buf; //这里const修饰pbuf可认为pbuf的内存是不可重分配的,用这种指针的时候必须初始化。

    这时候,如果char *buf2[] = “def”;pbuf = buf2 //这是错的,

    但,pbuf[i] = ‘a’是对的。(当然i不能越界)

    如果这样写,char  *const   pbuf = "hello";  pbuf[i] = 'a'; //是错的,

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    下面是引用的别人的为啥?

    char *p="hello";不应该存在于今天的C++程序中了。
    这种写法完全是为了保持对C中过去通行的(错误的)
    写法的兼容性而对C++类型系统不得已的破坏。

    不仅从原理上毫无道理,正如RoachCock所言,由p改写"hello"会直接引发CPU异常。此写法已被声明为deprecated,这意味着在未来的某一天你的程序将不能通过编译。


    可以这样理解:这句话
    char* p="abc";里的"abc"并非常量,而是以常量区的"abc"为源,在栈区里新申请的一个空间

    虽然和书上的理论不符,但编译器是怎么做的就难说了

    因为指向常量的指针不能够自动转换成不是指向常量的指针,反之则可以
    -----------------------------------------------------------------
    我也觉得这个原因, 觉得VS2005的结果没有错.
    编译器对语法的具体实现仁者见仁了。
    GCC里输出为
    foo( const char* )
    catch( const char* )

    指出一点:C++标准规定,字面字符串常量,像"abc",属于const char *。这一点是没有疑问的。但是现存的char *p = "hello,world",这样的代码太多了,如果严格按照标准来这种初始化是不能成立的,所以C++标准网开一面(还是为了向下兼容),特别允许这种语句合法。或者说,法外施恩来保证那些像楼主所说的char *到const char *的自动转换能够进行。但这不表明"abc"就是char *了,如果char *p = "abc",若试图修改p[0]就会引发一个段错误。关键在于"abc"存放于全局数据段。可以拿下面一个例子看:

    #include <cstdio>

    void f()
    {
    char *p = "abc";
    std::printf("%p\n",p);
    }

    int main()
    {
    char *p = "abc";
    std::printf("%p\n",p);
    return 0;
    }
    运行一下看看,两个p指向的是同一个地址。之所以编译器能这样做,就是因为字符串常量是const char *,是一个imutable对象。虽然可以被转换为char *,但这样做无疑是有危险的。可以在上面的main函数里添加一个p[0] = 'b',马上会导致一个runtime error,如果是linux的话会告诉你是一个段错误。

    指出一点:C++标准规定,字面字符串常量,像"abc",属于const char *。

    ----------

    我觉得允许
    char *p = "abc";
    这样的声明有点误导人的意思,但是好像教材上都没提出过这一点

    刚刚运行了下面这段程序:
    int main()
    {
    const int a = 8;
    int *p = const_cast<int*>(&a);
    *p = 9;

    cout << a << endl;
    cout << *p << endl;
    cout << &a << endl;
    cout << p << endl;

    return 0;
    }
    结果是:8, 9,0x12FF7C,0x12FF7C
    虽然地址一样,但是a还是8,并没有象lz说的那样a会变成9

    地址应该是0x0012FF7C,写掉了2位

    好久没上C/C++板块,还是有一些很不错的讨论
    收藏先

    我觉得这并不是一个很大的错误/问题,就像guqst(pop) 说的,仁者见仁,智者见智罢了。
    在编译器设计上差异而已,对于应用并没有很大的影响。

    我有个同事说,CSDN上太学究了,差不多说得就是这吧?

    我个人认为这不是一个bug,理由如下:

    首先可以确定的是,"abc"这样的一个字符串确实是放在常量数据区的,我们可以在初始化的时候这样进行:char *p = "abc";

    这时候p指向的地址和函数地址在数值上很接近,这说明p指向了代码区。

    这样做的原因是为了向下兼容,因为C89上没有const的概念,所以很多初始化的时候都是这么调用的,如果C++不允许这么做的话,在移植方面就会出现很多的错误,导致C程序员不愿意将改用C++。这是C++语言为了生存所做的妥协。

    那么如下的调用呢?char p[] = "abc";

    这没有任何问题,首先你声明了一个数组,然后将数组的大小定义成刚好能存下"abc"字符串,并且就真的存放了"abc"在里面,这时你的数组数存放在数据区的,并且已经在数据区分配了相应的空间,不论是全局的也好,还是自动的也好。

    C++还有一个规则就是,非常量指针可以隐式转换成常量指针,而反之则需要显示转换。如:

    char a[10];
    const char *p = a;

    这是没有问题的,但这么做只是说当我用p来操作这个地址中的数据时,我只想进行读操作,这样做相当于编译器帮你做了一部分代码检查工作,防止你在用p操作地址时错误的修改了地址中的数据。但p指向的地址并不是在代码区,这和char *p = "abc"有很大的区别。

    反过来:

    const char *p = "abc";
    char *a = p;

    这是不允许的,需要进行转换:char *a = const_cast<const char*>p;

    说明这是我想要的强制转换,但如果这时你调用a[0] = 1;这样的操作,还是不会成功。

    既然我们都能理解foo(char*)和foo(const char*)是怎么共存的了,那么如果按照如下调用:

    try
    {
    throw "abc";
    }
    catch (const char*)
    {
    cout << "catch(const char*)" << endl;
    }
    catch (char*)
    {
    cout << "catch(char*)" << endl;
    }

    抛出的异常将永远被const char*截获,由于char*可以隐式转换成const char*,所以编译器会通知说,有一段异常处理代码永远不会运行到。

    如果我们如下调用:

    try
    {
    char *p = "abc";
    throw p;
    }
    catch (char*)
    {
    cout << "catch(char*)" << endl;
    }
    catch (const char*)
    {
    cout << "catch(const char*)" << endl;
    }

    增加一个指针的声明,我们就会发现,运行的效果是一样的。这就是为什么会让catch(char*)截获了的理由:

    当异常抛出的时候,它首先走到了catch(char*)这个分之,它首先要进行初始化尝试,看是否可以将异常初始化成char*,由于以上所说,在初始化的时候,C++的编译器是允许将常量字符串赋值给一个非常量指针的,所以以上的异常将被第一个catch截获。

    相同的例子:

    foo(char *)
    {
    char *p = "abc";
    }

    当我们这么调用:foo("abc"),在函数调用时,不论是参数的传递,还是局部变量的初始化,都可以看作是存放在堆栈内的变量的初始化,所以常量字符串可以在初始化的时候传递给非常量字符串。

    当然如下的声明更好:

    foo(const char *)
    {
    const char *p = "abc";
    }

    const是编译器的一个关键字,用来限制对其后声明的变量的操作。

    bcc 5.82 输出是:
    foo( char* )
    catch( char* )

  • 相关阅读:
    yzoj P2344 斯卡布罗集市 题解
    yzoj P2350 逃离洞穴 题解
    yzoj P2349 取数 题解
    JXOI 2017 颜色 题解
    NOIP 2009 最优贸易 题解
    CH 4302 Interval GCD 题解
    CH4301 Can you answer on these queries III 题解
    Luogu2533[AHOI2012]信号塔
    Luogu3320[SDOI2015]寻宝游戏
    Luogu3187[HNOI2007]最小矩形覆盖
  • 原文地址:https://www.cnblogs.com/smileallen/p/3391595.html
Copyright © 2011-2022 走看看