阿里云建设网站的步骤,设计师投稿网站,网站网站环境搭建教程,室内设计培训班学费一般多少要点#xff1a;char* nameabc指的是常量字符串#xff0c;不可以修改指针#xff0c;是兼容老的写法#xff1b;char[] nameabc是指针#xff0c;可以修改#xff1b;
在学习过程中发现了一个以前一直默认的错误#xff0c;同样char *c char* nameabc指的是常量字符串不可以修改指针是兼容老的写法char[] nameabc是指针可以修改
在学习过程中发现了一个以前一直默认的错误同样char *c abc和char c[]abc,前者改变其内容程序是会崩溃的而后者完全正确。程序演示测试环境Devc代码 #include stdio.h#include string.hmain()...{ 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 abc2293624 2293624 abc2293620 4199056 abc
参考资料首先要搞清楚编译程序占用的内存的分区形式一、预备知识—程序的内存分配一个由c/C编译的程序占用的内存分为以下几个部分1、栈区stack—由编译器自动分配释放存放函数的参数值局部变量的值等。其操作方式类似于数据结构中的栈。2、堆区heap—一般由程序员分配释放若程序员不释放程序结束时可能由OS回收。注意它与数据结构中的堆是两回事分配方式倒是类似于链表呵呵。3、全局区静态区static—全局变量和静态变量的存储是放在一块的初始化的全局变量和静态变量在一块区域未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。4、文字常量区—常量字符串就是放在这里的。程序结束后由系统释放。5、程序代码区这是一个前辈写的非常详细//main.cpp
#include stdio.h#include string.h int a0; //全局初始化区 char *p1; //全局未初始化区 main() ...{ int b;栈 char s[]abc; //栈 char *p2; //栈 char *p3123456; //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 *s2bbbbbbbbbbbbbbbbb;aaaaaaaaaaa是在运行时刻赋值的而bbbbbbbbbbb是在编译时就确定的但是在以后的存取中在栈上的数组比指针所指向的字符串(例如堆)快。比如#includevoidmain(){char a1;char c[]1234567890;char *p1234567890;a c[1];a p[1];return;}对应的汇编代码10:ac[1];004010678A4DF1movcl,byteptr[ebp-0Fh]0040106A884DFCmovbyteptr[ebp-4],cl11:ap[1];0040106D8B55ECmovedx,dwordptr[ebp-14h]004010708A4201moval,byteptr[edx1]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 a1; char c[]1234567890; char *p1234567890; a c[1]; a p[1]; ...
a c[1];要比a p[1];快的原因少了一条指令嘛关于指针的论述1.指向常量的指针。
char buf[ ]“john”
const char *pbufbuf可以认为这个const修饰的是cahr所以char类型是常量
即 如果想要这么做*(pbufI) 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 *phello;不应该存在于今天的C程序中了。这种写法完全是为了保持对C中过去通行的错误的写法的兼容性而对C类型系统不得已的破坏。
不仅从原理上毫无道理正如RoachCock所言由p改写hello会直接引发CPU异常。此写法已被声明为deprecated这意味着在未来的某一天你的程序将不能通过编译。
可以这样理解这句话char* pabc;里的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_castint*(a);*p 9;
cout a endl;cout *p endl;cout a endl;cout p endl;
return 0;}结果是8 90x12FF7C,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_castconst 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* )