用户操作
[即时聊天] [发私信] [加为好友]
邓际锋ID:soloist
84661次访问,排名1139好友1人,关注者2
soloist的文章
原创 39 篇
翻译 0 篇
转载 0 篇
评论 189 篇
soloist的公告
欢迎吹毛求疵,感谢您对任何错误的指正,包括技术的、语法的、用词的、标点的、典故的、引用资料的……
最近评论
qingbai:lua绝对是个好东西。但国内除了java就是.net,其他东西没法活。因为程序员得工作,得吃饭。国内有哪家公司用lua?唉没办法呀。国外是一片繁荣,“百家争鸣”,国内是“青一色”的java和.net!无奈!
zhangyilan:尽管没有在实际代码的编写中碰到这个问题,不过也先学习一下,免得出现问题了搞出清楚情况。
ddrmsdos:这篇文章写的太好了,分析的非常仔细,以前常常碰到这类问题,终于解了我多年的心头之患......
ollydbg23:楼主的这篇文章写的非常好啊!
我看了以后,还是挺有收获感的,多谢多谢!
我也是对汇编,c++的比较感兴趣,有空可以交流一下!
w2001:写得很好
文章分类
收藏
    相册
    好博链接
    C++罗浮宫
    cpper
    fixopen
    fmddlmyy
    neoragex2002
    whinah
    云风
    梦想风暴
    沉思者
    许式伟
    负暄琐话
    辣子鸡丁
    存档
    软件项目交易
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 C/C++中数组名的含义收藏

    新一篇: 整型表达式比较的潜在危险 | 旧一篇: MFC中菜单项的动态添加、删除与响应

        C/C++中的数组名是个很奇怪的东西,它到底代表什么呢?
       
        对于char array[n](n是一个常数),大概有这么几种语义:
        <1> char* const(注意不是const char*)   <2> char [n]

        举例如下(WIN2000 PRO平台,VC.NET 7.1下编译):

    <1> char *p = array; //array表示char* const,p得到的是数组的首地址

        size_t size = sizeof(char [n]); // size等于n

    <2> char (*p)[n] = &array; // array表示char [n],
                               // p得到的仍然是数组的首地

            
        char (*q)[n] = array;  // 编译错误

        char (*r)[n] = (char (*)[n])array; // r得到的是array数组的首地址

    <3> char (&p)[n] = array;  // array表示 char [n]

    <4> void foo(char a[n])
        {
          int size = sizeof(a); 
    // size == 4(32位系统),
                                 // 因为a实际上表示的是char
    *
        };

        foo(array);            // array表示char* const

    <5> void foo(char (&a)[n]);
        {
           int size = sizeof(a);  
    // size == n
        };

        foo(array);           // array表示char [n]

    <6> void foo(char (*a)[n]);
        {
           int size = sizeof(*a);   // size == n
        };

        foo(&array);           // array表示 char [n]

    <7> char *p;
        array = p; 
    // 编译错误"error C2440,无法从char*转化为char [n]",
                    // 因此array表示char [n]

    <8> char other[n];
        array = other;  
    // 编译错误"error C2106, '='左操作数必须为L值", 
                         // 因此array表示char [n]

        (char (&)[n])array = other; 
       
    // 执行完后array的头4个元素表示的32位数与other代表的数组首地址相同,
        // 在这里other被解释成char* const

        (char (&)[n])array = (char [n])other;
        // 执行完后array的头4个元素表示的32位数与other代表的数组首地址相同

        (char (&)[n])array = (char (&)[n])other;
        // 执行完后array数组与other数组的头四个元素相等

        (char (*&)[n])array = (char (*)[n])other;
       
    // (char (*&)[n])表示的是一个引用类型,
        // 这个引用关联到一个指向char[n]数组的指针,
        // 执行完后array的头4个元素表示的32位数与other代表的数组首地址相同

        (__int64&)array = (__int64&)other;
        // 执行完后array数组与other数组的头8个元素相等

    <9> long i = 0;
        (long &)array = i;  
    // 实际改变的不是array本身的值,
                            // 而是它代表的数组中的头4个元素(32位),
                            // 因此array代表的是char [n]

    <10> long i = 0;
         (char (&)[n])i = array;
    // 假设array数组首地址为0x0012feac,
                                 // 则指令执行后i == 0x0012feac

    <11> long i = 0;
         (char (&)[n])i = (char (&)[n])array;   
         // 执行后i的值等于array头4个元素代表的32位数(32位系统)
       
    <12> (char *&)array = "string";     
         
    // 执行后array头4个元素代表的32位数与
          // "string"常量字符串在内存中的地址相同

    <13> (char (&)[n])array = (char (&)[n])"string";
         // array数组的头4个元素依次为's','t','r','i'

        当我们进行(char [n])array这样的强制转换时,效果与(char* const)array转换相当,都被解释成表示数组首地址的指针。但是两者还是有微妙区别的:sizeof(char [n])等于n,sizeof(char* const)等于4(32位系统),而且象(char [m])array这样的转换就不允许,其中m不等于n。如果我们用某种引用类型强制转换数组名时,编译系统会将转换结果(引用类型)自动关联到从数组首地址开始的内存区,而非数组名本身所在的内存区(它是否真的存在于内存中都是个未知数)。当我们用这样强制转换过的数组名做赋值操作的左操作数时,改变的就是数组名代表的数组内存区了,而被改变的内存区的大小就要视引用类型而定,比如__int64&,那么大小就是8字节,其余类推。因为(char [n])(char* const)效果基本相当,结果都被解释成指针,所以(char (&)[n])(char* const &)也基本相当,结果就被解释成关联到指针的引用。当用(char (&)[n])array做赋值操作的右操作数时,实际上会从array数组首地址开始的内存区读sizeof(char* const)大小的数据,然后赋值给左操作数。这就可以解释为什么(char (&)[n])array = (char (&)[n])other执行后array与other的头4个元素相等了。

     

        补充于2004年12月19日:
        我总结的语义恐怕还不够准确,现在把ANSI C标准中的说法摘录如下: (1)当一个数组标识符出现在表达式中,这个标识符的类型就从“某种类型T的数组”转换成“指向类型T的指针”,而且它的值就等于数组第一个元素的地址。但是当数组标识符被用作sizeof和取址(&)操作的操作数时,sizeof返回的是整个数组的大小,而取址操作返回的是指向数组的指针(而不是指向一个值为数组头元素地址的指针的指针)。(2)下列表达式不能产生lvalue: 数组名,函数,枚举常量,赋值表达式,强制类型转换,函数调用。

        根据以上说法基本可以推断C程序中数组名的语义,但是这并没有涵盖C++,我手头没有ISO C++标准,所以自己也很不确定。不过可以肯定,C++因为有引用类型,所以情况要复杂。比如对于C中不会产生lvalue的赋值表达式,C++就有不同的解释。

    int a;
    (a = 10) = 1000;

    这两行代码在C中非法,但是在C++中完全合法。

        另外,有人说gcc对(5)也会编译报错,经作者用MinGW3.1.0(g++ 3.2.3)测试发现不会。因为(5)中不会产生临时变量,仅仅是用了array来初始化函数中的局部引用类型变量a,这是完全合法的动作。

        补充于2005年7月1日: 用MinGW3.1.0(g++ 3.2.3)编译,只要涉及到(char (&)[n])array的强制转换均通不过,错误信息为: cannot convert `char*' to `char[n]' in converting。同样,(char* &)array也出错,因为array是一个类型为char*的rvalue,不允许转换为non-const引用类型,而改成(char* const &)array便可行,但相应的 (char* const &)array = "string" 则对array数组没有任何影响(为什么?估计是因为产生了一个(char * const &)类型的临时变量,对这个变量做初始化,所以对array没有任何影响)。

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

    新一篇: 整型表达式比较的潜在危险 | 旧一篇: MFC中菜单项的动态添加、删除与响应

    评论

    #soloist 发表于2004-11-29 14:38:00  IP: 218.78.225.*
    cha (*q)[n] 实际上定义了一个指针,这个指针指向的是一个有n个char类型元素的数组,对于 char (*q)[n] = array; 这样一个表达式,array代表的是数组本身,而不是数组的地址(注意这个概念与数组首元素地址不一样,它代表的是数组作为一个整体在内存中的地址),所以必须得用一个取址运算符。

    如果你写 char q[n] = array; 这样的表达式,实际是告诉编译器用array来初始化q这个数组,这是非法的。
    #Soundboy 发表于2004-11-29 09:40:00  IP: 211.94.131.*
    请问:
    char (*q)[n] = array; // 编译错误

    错误原因是什么,是没有定义q 吗?
    #Soundboy 发表于2004-11-29 09:59:00  IP: 211.94.131.*
    不好意思,上面说错了(还不能删除,晕)


    char q[n] = array; 这样是不是可以的
    #Soundboy 发表于2004-12-01 10:41:00  IP: 211.94.131.*
    这样的代码在Unix下用gcc是编译不过的:
    const int n = 10;
    char array[n];
    char other[n];

    int main()
    {
    (char(&)[n])array = other;
    return 0;
    }


    错误提示是:
    main.cpp:16: error: conversion to non-const reference type `char (&)[10]' from rvalue of type `char*'


    而在VC下是可以编译的,说明这个语法现象是个不确定的。我还是没能很清晰。

    不过老兄研究这种问题的时候也最好多试验几种编译器。

    #Soundboy 发表于2004-12-01 09:49:00  IP: 211.94.131.*
    你的意思就是说 array指的是整个数组(如果我们把这个数组看成一个变量的话)。

    &array就是这个变量(整个数组)的首地址了。

    这个和我以前脑子里的概念好像不一样,我马上又赶快看了看书,随便找了本机械工业出的《C语言解析教程》,P194上写到:数组名是一个特殊的固定地址,可以看作是常量指针。

    这么说 array既代表数组变量(假设可以这么说), 又代表数组的第一个元素的地址了?
    #Soundboy 发表于2004-12-01 10:21:00  IP: 211.94.131.*
    <4> 和 <5> 的size值居然不一样,这个也有些没有想到。
    其中的深层次原因还有待思考
    #soloist 发表于2004-12-01 15:25:00  IP: 218.78.227.*
    1、的确,我在思考这个问题的时候方向不对。我是根据具体编译器的表现来推断语言中某种语法构造的语义,这很危险,因为这样得出的结论是依赖于编译器实现的。而正确的做法就是参考ANSI C标准,得到最权威的解释,然后在不同的编译器中验证它们与标准的符合程度。谢谢你提供的信息,我会参照C标准来检讨我的认识。

    2、“<4> 和 <5> 的size值居然不一样,这个也有些没有想到。
    其中的深层次原因还有待思考 ”

    在《The C Programming Language (second edition)》的5.2节有这么一段话:"As formal parameters in a function definition,
    char s[]; and char *s; are equivalent; we prefer the latter because it says more explicitly that the variable is a pointer."

    根据这段话可以知道<4>中的void foo(char a[n])与void foo(char *a)没有任何区别。引用是C++引入的新类型,在C中不存在,void foo(char (&a)[n])实际上在函数体中定义了一个引用变量a,且它与一个有n个元素的char数组相关联,也就是说a代表的是这个n元数组整体,而对a施加的任何操作都会自动地转到它所代表的对象上去,此时sizeof(a)就相当于sizeof(char [n]),结果当然就是n了。
    #十年未晚 发表于2004-12-15 01:57:00  IP: 134.225.187.*
    好文章!
    结合前几天看的左右值,临时变量,非const的引用变量不能接受右值等,现在终于基本对数组参数传递的各种情况比较清楚了---也只是比较:)

    你说的char array[n]有两种语义是个不错的辨别方法,但在使用时比较混乱---到底是算char * const 呢还是char [n]啊?
    方法好像只有两种都代进去试,那种说的通就是那种,否则就语法错误

    对于Soundboy的例子,你用的是gcc哪个版本?
    如果我没猜错---我没有gcc:(---所有类似的强制转换,也就是<5>和<<8>以后的都应该通不过编译.
    因为gcc在这里产生了一个实际的临时变量,临时变量是右值,而按标准右值是不能绑定到非const的引用变量的.
    试一下
    (char(& const)[n])array = other;
    看能不能通过?

    至于VC是偷了个懒,并没有产生实际的临时变量,只是将"临时变量"的地址设的和数组地址---或者传进函数的实参地址---一样,所以通过了编译

    好像gcc老一点的版本也用了这种方法,不清楚,所以问你版本
    #soloist 发表于2004-12-19 16:35:00  IP: 218.78.226.*
    我总结的语义恐怕还不够准确,我现在把ANSI C标准中的说法摘录如下:
    ◎ 当一个数组标识符出现在表达式中,这个标识符的类型就从“某种类型T的数组”转换成“指向类型T的指针”,而且它的值就等于数组第一个元素的地址。但是当数组标识符被用作sizeof和取址(&)操作的操作数时,sizeof返回的是整个数组的大小,而取址操作返回的是指向数组的指针(而不是指向一个值为数组头元素地址的指针的指针)。◎
    ◎ 下列表达式不能产生lvalue:数组名,函数,枚举常量,赋值表达式,强制类型转换,函数调用。◎

    根据以上的说法基本可以推断C程序中数组名的语义,但是这并没有涵盖C++,我手头没有ISO C++标准,所以自己也很不确定。不过可以肯定,C++因为有引用类型,所以情况要复杂。比如对于C中不会产生lvalue的赋值表达式,C++就有不同的解释。
    int a;
    (a = 10) = 1000;
    这两行代码在C中非法,但是在C++中完全合法。

    另外,你对GCC表现的分析有道理,但是说GCC对(5)也会编译报错,我推想应该不会吧。因为(5)中不会产生临时变量,仅仅是用了array来初始化函数中的局部引用类型变量a,这是完全合法的动作。
    #step_by_step 发表于2005-06-30 20:26:00  IP: 61.186.252.*
    //FYI.You can use it to get the size of an array

    template<typename T, size_t N>
    inline size_t count_of(T (&)[N])
    {
    return N;
    }
    发表评论  


    当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
    Csdn Blog version 3.1a
    Copyright © soloist