陈皓专栏 【空谷幽兰,心如皓月】

芝兰生于深谷,不以无人而不芳;君子修道立德,不为困穷而改节。

用户操作
[即时聊天] [发私信] [加为好友]
陈皓ID:haoel
486244次访问,排名97好友62人,关注者82
芝兰生于空谷,不以无人而不芳;君子修道立德,不为困穷而改节。
haoel的文章
原创 71 篇
翻译 0 篇
转载 0 篇
评论 1057 篇
陈皓的公告
Email & MSN
haoel@hotmail.com
最近评论
elovenana:按照这样的写法,
(int *)*(int *)(&b)你的意思是说:类的首字节里面,写的是虚表的首地址;
(int *)*(int *)*(int *)(&b),虚表的首地址里面,放的是首方法的地址?

这种类似于,二级指针似的,访问方法,只是茅塞顿开
nanyangyeren:老大,写的太好了!怪不得别人抄袭呢,赫赫。
spidertiger:另外,“尽管Stallman既不是、也从来没有成为一个Unix程序员”可能不对吧,RMS 以前维护着 Emacs,好像刚从去年他把该项目转交给其他人。
spidertiger:楼主,lovekatherine 说得没错。RMS 倡导的是 Free Software, 而 Open Source 是 Eric S. Raymond 提出来的,他是《大教堂和集市》的作者。

自由软件和开源软件从概念上就不一样,http://www.zeuux.org/philosophy/rms-and-fsm.cn.html 上解释了它们的关系(文中一灰度色带的……
spidertiger:@jacklondon:可笑,楼主说的是在程序中想更改文件权限的问题,你理解成什么啦?不通过程序改个权限问题谁不会呀?!

@KX.C:你对 Windows 很熟悉. 可惜对于 UNIX 并不熟悉~
文章分类
收藏
    相册
    我的BLOG
    耗子小筑(非技术)(RSS)
    陈皓专栏(技 术)(RSS)
    存档
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 从语句 char* p="test" 说起收藏

    新一篇: STL 的string类怎么啦? | 旧一篇: C/C++返回内部静态成员的陷阱

    从语句 char* p="test" 说起
     
     
     
    我相信,使用C/C++多年的人对下面这个字符串赋值语句都不会陌生吧。
     
                  char* p = "test";
     
    同时,我也相信,各位在使用这种语句后吃过很多苦头也不少吧?只要你想利用指针p来改变字符串的内容,你的程序都会得到一个让你颜面尽失一个内存非法操作。比如,下面的这些语句:
     
                  p[0] = 's';
                  strcpy(p, "haoel");
     
    原因就在于,char* p = "test"; 这个声明,声明了一个指针,而这个指针指向的是全局的const内存区const内存区当然不会让你想改就改的。所以,如果你一定要写这块内存的话,那就是一个非常严重的内存错误。另,之所以加粗“全局const内存区”,是强调一下,如果你不信的话,你可以试试下面这段代码,看看p1p2的地址是不是一样的。
     
                  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
           这种featureC89中还可以使用,但在C99C++中都被去除了。(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++中的constnon-const的语义,任何想要破坏这个语义的事情都会给我们带来未知的结果。
     
    (转载时请注明作者和出处。未经许可,请勿用于商业用途)
    更多文章请访问我的Blog: http://blog.csdn.net/haoel

    发表于 @ 2006年11月19日 03:29:00|评论(loading...)|编辑

    新一篇: STL 的string类怎么啦? | 旧一篇: C/C++返回内部静态成员的陷阱

    评论

    #htqx 发表于2006-11-19 13:53:00  IP: 59.53.209.*

    char p[] = “test”;

    似乎没有什么问题。并且,也可以理解。虽然文字是常量,但是初始化却又是一种情况,可以用文字来初始化常量,也可以用来初始化变量。数组和指针的区别是数组更像是普通类型,有地址有长度,可以分配合适的值。

    static int i;
    static void func()

    这个定义好像没有什么必要修改阿。
    #fengj 发表于2006-11-19 16:17:00  IP: 219.220.210.*
    这样的伪例子在符合标准的编译器一个也编译不成功。
    #陈皓 发表于2006-11-19 18:05:00  IP: 125.33.224.*
    qiufengxiyu,您好,非常对不起,这是我的一个错误。关于char str[] = “hello world”;是对的,这是一个数组初始化,不是一个把const转成non-const的例子。真对不起。你看我的文章很细,谢谢您的指正。(我已从文删除了这一错误)
    #阳光 发表于2006-11-19 19:24:00  IP: 58.61.99.*
    “更改父类成员的存取权限”这一点会被废弃我个人不认同,原因是当子类重写父类方法时,这个时候,父类中声明的通常是public类型,因为父类的方法需要曝露给外部,给子类只是根据多态性原则重写父类方法罢了,根据访问权限最小化原则,子类的方法通常可以设置为private或protected,在实际中,很多情况下都需不需要将子类重写的方法访问属性设置为和父类相同的public类型,否则也不符合面向对象的封闭原则
    #qiufengxiyu 发表于2006-11-19 15:02:00  IP: 162.105.80.*
    (1)取而代之的,应该是使用数组来做初始化声明。如:char str[] = “hello world”;

    (2)一、 隐晦的字符串的const转换。
    char *p = “test”;
    w_char *pw = “test”;
    char p[] = “test”;
    把一个const的字符串类型转成non-const的。包括指针和数组。

    (1)和(2)都是文中提到的,到底用数组来做初始化声明,如char str[] = “hello world”是对的吗?
    #qiufengxiyu 发表于2006-11-19 15:19:00  IP: 162.105.80.*
    陈老师,您好,我想向您请教《从语句 char* p="test" 说起》一文中的一个问题。

    您在该文中先是说:
    取而代之的,应该是使用数组来做初始化声明。如:char str[] = “hello world”;

    后来您在该文中又说:
    一、隐晦的字符串的const转换。
    char *p = “test”;
    w_char *pw = “test”;
    char p[] = “test”;
    把一个const的字符串类型转成non-const的。包括指针和数组。

    我想请问一下使用数组来做初始化声明,如char str[] = “hello world”到底是好不好,谢谢!
    #剑神一笑 发表于2006-11-20 00:27:00  IP: 220.113.56.*
    我相信,使用C/C++多年的人对下面这个字符串赋值语句都不会陌生吧。

    char* p = "test";

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

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

    补充一点,其实是把字串放在了一个只读的页中,改一下页保护属性就可以了,也不会报错。不过建议不用
    char *p = "test";
    unsigned long od;
    VirtualProtect(p, 4, PAGE_READWRITE, &od);
    p[1] = 'a';
    std::cout << p;
    #陈皓 发表于2006-11-20 11:00:00  IP: 125.33.224.*
    更正了一下文中的一个错误:

    对于"更改父类成员的存取权限",那个例子中子类的定义从
    class D : public B
    {
    public:
    int i;
    };


    改为:

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

    对于前者,子类中的声明遮盖了父类的声明,对于i来说也就是两个成员,父类的和子类的各是各的。但对于后者而言,是同一个,但存取权限被修改为public了。

    对此笔误,我向各位道歉。


    对于“阳光”网友所说的虚函数的问题,是另一个问题。
    #xubai 发表于2006-11-20 11:15:00  IP: 218.249.224.*
    有什么意义,只要知道二进制格式得人都哦明白为什么
    大不了用.section放到别处去,就不会出什么问题了.没有必要大惊小怪
    #求职,还是为了求职 发表于2006-11-20 11:50:00  IP: 222.94.63.*
    看都看腻了的话题
    #aa 发表于2006-11-20 12:16:00  IP: 61.133.25.*
    问题是c++发明者本身已经提出这个用法是一种特殊的,而绝大多数程序员都了解。既然说是特殊用法,又不是错误用法,为什么禁止不用?而且所有的C++著作中都提到了 char* p = "test"
    这个p是不能再赋值的,你为什么还要写
    strcpy(p, "haoel");这样的语句?

    总之,用这种方法的人没错,虽然不建议使用,但使用了绝对没有错,看到这种写法还想去改指针的人才有错。

    不信你在论坛上问一下,除了作者你写出:
    p[0] = 's';
    strcpy(p, "haoel");

    还有多少人写过这样的语句?
    C++原作者的书回家好好看看去。
    #Lff 发表于2006-11-20 14:37:00  IP: 202.99.4.*
    "非法操作" 并不是在程序部分出的,而是操作系统抛出的异常。原因是,例如对于VC来说,字符串常量放在了.CODE节中,而这个节是只读的(因为代码也在这个节中,如果不是的话,就可以在程序中直接修改程序代码了)。

    如果想让下面的程序非法操作不再出现,很简单,使用LordPE等工具,修改.CODE节的属性,改为可读可写就行了。不过如果在子程序中,例如下面的程序:
    void test() {
    char* a = "test";
    printf("a=%s, a[1]=%c, addr=%0x \n",a, a[0], a);
    a[0]='g';
    }
    int main(int argc, char* argv[])
    {
    test();
    test();
    getchar();
    return 0;
    }
    两次调用,打印出来两个不同的字符串(test和gest),这和程序的初衷是违背的。

    比较有意思的是,上面的程序,在VC6的DEBUG版下面,字符串常量是放在.CODE节中的,但是在RELEASE版中,因为要优化,VC编译器将所有的节压缩为1个,也就是这个唯一的节中有.CODE , .DATA等等,这个压缩后的节是可读可写的,所以上面的程序对于RELEASE版即使不用LordPE改节属性,也能正常结束而不出现非法操作。不过仍然的,两次调用打印出来两个不同的字符串。
    #BCB 发表于2006-11-20 19:45:00  IP: 219.130.16.*
    我在BCB中试上面的代码
    char* pStr="test";
    pStr[1]='d';
    std::cout<<pStr<<std::endl;
    strcpy(pStr,"oh!yes");
    std::cout<<pStr<<std::endl;

    上面在CB下运行通过,而且没报任何错误,运行也正确,在VC就报错误了
    #陈皓 发表于2006-11-20 22:16:00  IP: 125.33.224.*
    Lff 和 BCB,感谢你们的回复。看来,不同的C++编译器会得到不同的结果。可见这个feature有多么的不确定性,所以,为了程序的可移值性和标准性,还是不要使用这种feature的好。
    #钢狼 发表于2006-11-21 09:56:00  IP: 218.19.45.*
    char* p1 = "anything";
    char* p2 = "anything";
    上面的p1和p2未必就是指向不同的地址,取决于编译器的选项.
    如果编译器支持合并相同字符串,并且打开了该关的话,上面的
    语句将导致程序编译下来只有一个anything字符串.

    char *p1 = "anything";

    char p2[] = "anything";
    其实还有一个隐含的区别:
    那就是
    对于char p2[] = "anything";
    编译器将在栈中分配anything对应的空间,然后隐式执行
    memcpy函数,将常量anything拷贝过来.如果p2所指向
    的字符串比较长,并且对应函数执行频度比较高的话,其开销
    不小.
    char *p1 = "anything";则很简单,编译器只需在栈中分
    配一个指针变量的空间(32位环境下只需固定的4字节).然后
    将常量anything的地址赋值过来即可.
    #wadefelix 发表于2006-11-22 11:05:00  IP: 202.117.115.*
    char* pStr="test";
    pStr[1]='d';
    std::cout<<pStr<<std::endl;
    strcpy(pStr,"oh!yes");
    std::cout<<pStr<<std::endl;

    这段代码在DevC++中编译通过,但就是运行出错
    #wadefelix 发表于2006-11-22 11:10:00  IP: 202.117.115.*
    char* pStr="test";
    pStr[1]='d';
    单这两句也是一样
    编译通过,运行出错
    #野狼 发表于2006-11-23 11:34:00  IP: 203.86.81.*
    我觉得这个文章写得很差。看半天不知道什么意思。
    你是想说在VC里char* p = "test"; 和const char *p = "test";是一样的吗?还是想说编译器兼容的问题呢?如果这样。我觉得你都说错了。这两条语句有不同的意思,不是编译器的问题。
    另外,你也没有说出解决的办法,就说别人的说写得怎么不好,完全是偏激了。

    #clever101 发表于2006-11-23 14:41:00  IP: 222.35.173.*
    char* pStr="test";
    pStr[1]='d';

    我觉得可以从另外一个角度去看这个问题。pStr只是指向一个字符串,但它并没有拥有内存。没有拥有内存意味着它只能读内存而不能写内存。
    #bm1408 发表于2006-11-24 10:57:00  IP: 60.216.11.*
    func( char* p) { } // 以这种方式调用函数func(“abc”);

    try { thow “exception”; } catch (char* p) { }

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

    -----------------------
    方向弄错了吧(:
    就是char * 向const char *转换,这种隐式的转换是允许的~
    反过来就不允许了~

    #Leo 发表于2006-11-24 16:05:00  IP: 219.82.144.*
    这里面存在的主要问题是: 编译时问题 或 运行时问题。
    你写了这样的代码:
    char *p = "test";
    p[0] = 'a';

    这样的代码可能在编译时不会有任何提示,但在运行时会产生致命的错误。当然,这种形式能不用最好了,因为有时候晕了头,也会写出p[0] = 'a';这种语句。
    #IMGGTOO 发表于2006-11-25 23:38:00  IP: 202.100.200.*
    bm1408 发表于2006-11-24 10:58:00 IP: 60.216.11.*
    func( char* p) { } // 以这种方式调用函数func(“abc”);

    try { thow “exception”; } catch (char* p) { }

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

    -----------------------
    方向弄错了吧(:
    就是char * 向const char *转换,这种隐式的转换是允许的~
    反过来就不允许了~
    -----------------------------------------------------------------------
    楼住说的没错,是你自己看错了而已!
    func( char* p),要求的是 非 const的char类型的指针,但是,func(“abc”);这种调用中用的参数"abc",却是const类型,如果这个调用被允许的话,就相当于在调用的时候隐式把"abc"转换成的非 const类型了.

    象这种情况,C++ primer上推荐的是,最好把func定义成func(const char * p)这种模式.否则,在有些编译器中,类似于func(“abc”);这种调用是不能通过的!
    #yuanchuang 发表于2006-12-24 12:22:05  IP: 58.213.72.*
    CSDN网友,回帖表示对你的支持。
    你写的文章相当不错。
    #lbing7 发表于2007-01-24 12:05:54  IP:
    开始学习了...
    #yaotyl 发表于2007-01-25 11:30:14  IP: 218.28.148.*
    讨论很激烈 很好
    #lvcha412 发表于2007-03-02 11:21:38  IP: 210.82.49.*
    学习中.....
    #guogangj 发表于2008-01-28 15:07:01  IP: 61.172.24.*
    不错。

    char *pTest = "Test";
    pTest = new char[10];
    pTest[0] = '\0';

    如果这种情况的话就不能用const,C++是会带来很多不安全的因素,比如数组越界检查这种机制它都不具备,但这也正是C++强大的体现,它提供了最大的灵活性,可塑性和效率。

    类似的还有关键字mutable(允许const函数,又允许它访问非const成员的值),friend(允许封装,又允许破坏封装),reinterpret_cast(有类型安全机制,又允许任意指针转换),__declspec(novtable)(VC++特有,允许虚函数,又允许禁止虚函数)……另外我用VC写的程序从来就没打算要移植到别的系统,大量的Windows API的运用也不允许我移植,我的目标是做最好的Windows程序。

    关于const_cast的问题,我以前写过篇文章:http://blog.csdn.net/guogangj/archive/2007/03/29/1545119.aspx
    不知道对不对。

    最后:陈老师新年快乐:)
    #linyt 发表于2008-03-19 14:37:28  IP: 59.37.5.*
    # 陈皓 发表于2006-11-20 22:16:00 IP: 125.33.224.*
    Lff 和 BCB,感谢你们的回复。看来,不同的C++编译器会得到不同的结果。可见这个feature有多么的不确定性,所以,为了程序的可移值性和标准性,还是不要使用这种feature的好。
    -----------------------------------------------------------------------------------------------------------------------------
    C++是一个面向对象语言,但由于是从C发展过来的,所以它要向C兼容,所以它本身会存在很多不好的地方。C++经典的书中会介绍如何写比较安全的程(如用string类来代替c的字符串),这些都是我们写程序时候要注意的!

    更重要一点是C++只是一个标准,而不同的编译器的实现会有不同。我们写程序时不要使用编译器的某个特性,而非C++标准的,否则当遇到错误时会百思不得其解。

    虽然很多C++的问题可以采用平台依懒的方法来解决,但这样很不好!不知那天编译器版本更新,把实现方法作一个小的变更,或OS的实现方式也有变化的时候,你的程序会有出错的可能!

    楼主写此文是从C++的标准(C++ specification,可以这样说吧)出发,说一些C++的缺陷,建议大家不要使用未确定的语言特性。因为这些东西在不同的编译器里面的结果是不一样的!非常可怕!

    楼主此文写得比较好,不管从什么角度来看,上面的问题都值得大家注意!

    谢谢大家!
    #roc_won 发表于2008-03-31 13:15:11  IP: 220.231.32.*
    一直闹不懂为什么那么多程序员犯这个错.
    ISO C标准上写的明明白白, char* p = "test";是不可改变的常量, 改变其内容将导致不可预测是结果。所谓不可预测,就是各个编译器自行决定怎么处理。
    发表评论  


    登录
    Csdn Blog version 3.1a
    Copyright © 陈皓