QQ登录

只需一步,快速开始

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 1989|回复: 13

转陈皓一篇博文:从语句 char* p="test" 说起

[复制链接]
发表于 2011-1-9 21:11:25 | 显示全部楼层 |阅读模式
从语句 char* p="test" 说起

我相信,使用C/C++多年的人对下面这个字符串赋值语句都不会陌生吧。

              char* p = "test";

同时,我也相信,各位在使用这种语句后吃过很多苦头也不少吧?只要你想利用指针p来改变字符串的内容,你的程序都会得到一个让你颜面尽失一个内存非法操作。比如,下面的这些语句:

              p[0] = 's';
              strcpy(p, "haoel");

原因就在于,char* p = "test"; 这个声明,声明了一个指针,而这个指针指向的是全局的const内存区,const内存区当然不会让你想改就改的。所以,如果你一定要写这块内存的话,那就是一个非常严重的内存错误。另,之所以加粗“全局const内存区”,是强调一下,如果你不信的话,你可以试试下面这段代码,看看p1和p2的地址是不是一样的。

              char* p1 = "anything";
              char* p2 = "anything";
              printf(“ p1=%x, p2=%x \n”, p1, p2);

我想这应该是一个众所周知的问题吧。取而代之的,应该是使用数组来做初始化声明。如:char str[] = “hello world”; 如果现在还有哪本书中的C的示例采用了使用const字符串初始化指针的这种方式,那么你就可以把那本书撕了,如果这本书是C++的书话,那么你应该把这个作者和这个出版社告上法庭,因为你不应该容忍这种学术骗子。如果你的部门的开发人员还有人写出这种代码的话,如果他是C程序员,我想你可以在打过他的屁股后告诉他下不为例,如果他是一个C++程序员的话,我想你可以怀疑他是否有资格做一个C++程序员了。

       至于你问我为什么要对学C++的人那么苛刻,那是因为学过C++的人都知道C++中的const关键字的有着什么样的权力,你也应该知道C++对const有着无比的照顾和关爱,几乎所有关于C++的书都会提到const这东西。所以,如果作为一个C++的程序员来说,如果你不知道的话,那就太说不过去了。

       我们知道,双引号引起来的字符串是const的,所以,在C++的世界中,你应该进行如下的声明才比较稳妥:
            
              const char *p = "test";

这样,当你修改这个字符串的内容时,编译器会给你一个错误而导致你的程序编译不通过,从而不会产生运行时的内存错误。

       可问题是,像C++这种对类型要求很严格的语言来说,为什么它在编译诸如char *p="test" 程序的时候不出错,甚至连个警告都没有(g++和vc++7)?难道这是他的一个bug?我想,这应该是对古老的C的一个向下兼容。因为,在C的世界中,这种用法太多了。

在C++中,比如:函数的参数和异常的捕获都存在这种问题,如下所示:(因编译器而定,在gcc 3.4.3版中,下例中的异常示例不能被捕获,但VC++6中却可以被捕获)

       func( char* p) { }   // 以这种方式调用函数func(“abc”);
      
       try { thow “exception”; } catch (char* p) { }

       这些都是C++编译器默认了可以把const char* 转成 char* 的罪行,无疑会对大家是一个误导。甚至让人无所畏惧地走入其中,并自以为走入了正途。这样看来,这种向下兼容的C++标准,就显得有点误人不浅了。

       不过好在,C++标准委员会早已意识到了这一点。这个C++的feature被定义为了“Deprecated Feature”,即“不被建议使用的特性”。意思就是,在将来,这种特性将被从C++中移出,于是,你目前的这种程序将无法在新的C++编译器上编译通过。对于程序的可移植性来说,我们今天所写的代码尤其要注意这些“Deprecated Feature”。

       据我所知,目前C++中被列为“Deprecated Feature”如下所示(可能不准确,请大家指正)下面的这些feature都已被C++标准委员会订为废除featrue了。

一、           隐晦的字符串的const转换。

char *p = "test";
w_char *pw = L"test";

       把一个const的字符串类型转成non-const的。包括指针和数组。

二、           隐晦的类型声明。

func() {}   //函数的隐晦返回类型是int
static num;    //变量的隐晦类型是int
       这种feature在C89中还可以使用,但在C99和C++中都被去除了。(gcc 3.4版本对于这种声明会给出编译错误,而VC++6.0会认为这是合法的程序)

三、           布尔变量的累加操作。
bool isConn = false;
isConn++;          //这个操作会把isConn变为true
就目前而言,几乎所有的编译器都认可这种操作,但这种用法也是不被建议的,终有一天会被取消。

四、           更改父类成员的存取权限。

class B
{
    protected:
        int i;
};

class D : public B
{
     public:
        B::i; //这种方式可能大家很少看到。
};

       对于这种语法,子类重新暴露了父类的私有成员。这会带来很大的安全性问题。目前而言,这个feature对于所有的编译器来说应该都是可以编译通过的(连个Warning都没有)。但这个feature也是要被废除的。

五、           文件中域的static声明

static int i;
static void func()

       据说,这种旧的在C中的为了实现其作用域在本文件中的feature在未来的C++中也要被取消。


文章到这里应该结束了,在结束之前,让我再给大家共享一个有趣的关于const的例子(在网上看到的)

    const int a = 1;
    int *p = const_cast<int*>(&a);
    *p = 2;

    cout << “value a=”<< a << endl;
    cout << “value *p=” <<*p << endl;
    cout << “address a=” <<&a << endl;
    cout << “address p=” <<p << endl;
这段代码输出的结果如下:

value a=1
value *p=2
address a=0xbff1d48c
address p=0xbff1d48c

地址都是一样的,可值为什么不一样呢?呵呵。这个问题看起来有点“学术味”过浓,不过是个好例子,可以让你知道C++的一些用法和一些原理。有以下几个方面大家可以考虑一下:
1)const int a = 1是不是和宏有点像,会不会被编译器优化了?
2)去修改一个const的值,本来应该是不对的。这可能会是向旧的C兼容。是否会让编译器产生未知行为?

所以,这个示例也告诉我们,我们应该遵循C++中的const和non-const的语义,任何想要破坏这个语义的事情都会给我们带来未知的结果。

http://blog.csdn.net/haoel/archive/2006/11/19/1395358.aspx
更多文章请访问我的Blog: http://blog.csdn.net/haoel
 楼主| 发表于 2011-1-9 21:36:57 | 显示全部楼层
不过奇怪我给 char * 加上 const 定义编译器警告就闭嘴了
const char *sql=”旧内容“;
sql= "新内容";
char const *sql  也是同样的结果。。。

const 定义了指向字符串常量的指针的内容是可能改变的,这里面我的概念十分模糊,只好复习一下:

下面定义的一个指向字符串的常量指针:

  char * const prt1 = stringprt1;

  其中,ptr1是一个常量指针。因此,下面赋值是非法的。

  ptr1 = stringprt2;

  而下面的赋值是合法的:

  *ptr1 = "m";

  因为指针ptr1所指向的变量是可以更新的,不可更新的是常量指针ptr1所指的方向(别的字符串)。

  下面定义了一个指向字符串常量的指针:

  const * ptr2 = stringprt1;

  其中,ptr2是一个指向字符串常量的指针。ptr2所指向的字符串不能更新的,而ptr2是可以更新的。因此,

  *ptr2 = "x";

  是非法的,而:

  ptr2 = stringptr2;

  是合法的。

  所以,在使用const修饰指针时,应该注意const的位置。定义一个指向字符串的指针常量和定义一个指向字符串常量的指针时,const修饰符的位置不同,前者const放在*和指针名之间,后者const放在类型说明符前

[ 本帖最后由 haulm 于 2011-1-9 21:43 编辑 ]
回复

使用道具 举报

发表于 2011-1-11 00:57:21 | 显示全部楼层
可算是在这个板块看到篇看得懂的东西了。
回复

使用道具 举报

发表于 2011-1-12 01:11:54 | 显示全部楼层
C本来是给人类语言提供快捷的,结果让一些所谓的专家教授描得越来越神秘,到现在还是看不懂,基础的理解还没介绍就弄一堆高级的应用让人看,看不懂,只是从播布客视频上简单了解了一下C是汇编的打包重用才了解一点点C毛,以前想学单片机,看到汇编,就想汇编的一些功能段用来反复调用,写过后保留起来以后用到时直接粘贴就是了,C其实就是汇编的封装而已,暂时我还只能理解到C的毛,想研究到C的皮恐怕还需要一段时间,看到类似方程式的东西头就痛。
回复

使用道具 举报

发表于 2011-1-12 08:36:25 | 显示全部楼层
其实除了指针那里,C还是很好懂的,而且不用指针也没什么,一样可以写程序。
回复

使用道具 举报

 楼主| 发表于 2011-1-12 13:30:17 | 显示全部楼层
原帖由 huizige 于 2011-1-12 01:11 发表
C本来是给人类语言提供快捷的,结果让一些所谓的专家教授描得越来越神秘,到现在还是看不懂,基础的理解还没介绍就弄一堆高级的应用让人看,看不懂,只是从播布客视频上简单了解了一下C是汇编的打包重用才了解一点点C毛,以前想 ...

汇编才叫真的搞不懂,我收藏有一本书用汇编调用 win32api 写程序,那才叫绝。。。可惜8位汇编都没搞懂,32位的怎么学进去,现在流行64位了。
回复

使用道具 举报

发表于 2011-1-12 13:36:25 | 显示全部楼层
指针操作是区分c高手还是菜鸟的唯一标准。
汇编要精通计算机硬件、机器语言、底层才能学会。
回复

使用道具 举报

CecilHarvey 该用户已被删除
发表于 2011-1-12 16:57:51 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
回复

使用道具 举报

 楼主| 发表于 2011-1-12 20:10:04 | 显示全部楼层
C 接近于汇编,所以指针就显得很重要,不过我现在可能更依赖 Qt4 ,因为 C 的确有不少东西是需要自己实现,比如 itoa 居然也要自己写(linux 没有,虽然不难写...)。
回复

使用道具 举报

CecilHarvey 该用户已被删除
发表于 2011-1-13 13:14:36 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
回复

使用道具 举报

 楼主| 发表于 2011-1-13 13:30:11 | 显示全部楼层
原帖由 CecilHarvey 于 2011-1-13 13:14 发表
sprintf(buf, "%d", i);

那是打印输出,如果我有强制转换的需要呢?
回复

使用道具 举报

CecilHarvey 该用户已被删除
发表于 2011-1-13 14:37:57 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
回复

使用道具 举报

CecilHarvey 该用户已被删除
发表于 2011-1-13 14:40:24 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
回复

使用道具 举报

 楼主| 发表于 2011-1-14 23:13:23 | 显示全部楼层
原帖由 CecilHarvey 于 2011-1-13 14:40 发表

#include

main() {
  char buf[256];
  int i = 63232;

  sprintf(buf, "%d", i);
  puts(buf);
}

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

GMT+8, 2024-11-2 06:38 , Processed in 0.038481 second(s), 16 queries .

© 2021 Powered by Discuz! X3.5.

快速回复 返回顶部 返回列表