为什么 const 引用可以指向常量还可以取到地址? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ngg0707
V2EX    C

为什么 const 引用可以指向常量还可以取到地址?

  •  
  •   ngg0707 2018-09-24 00:09:59 +08:00 6209 次点击
    这是一个创建于 2643 天前的主题,其中的信息可能已经有所发展或是发生改变。
    #include <iostream> using namespace std; int main(int argc, char const* argv[]) { const int& a = 1; cout << &a << endl; return 0; } 

    不知道该怎么搜索这个问题,没查到。有人知道吗?

    44 条回复    2018-10-05 11:58:39 +08:00
    jmc891205
        1
    jmc891205  
       2018-09-24 00:23:41 +08:00
    编译器生成了一个临时对象
    ngg0707
        2
    ngg0707  
    OP
       2018-09-24 00:27:39 +08:00
    ```cpp
    #include <iostream>
    using namespace std;

    int main(int argc, char const* argv[])
    {
    int&& a = 1;
    cout << &a << endl;
    return 0;
    }
    ```

    这样居然也可以。
    ngg0707
        3
    ngg0707  
    OP
       2018-09-24 00:28:23 +08:00
    @jmc891205 您好,如果不这样做,是否编译器就不会给这个右值分配内存呢?
    jmc891205
        4
    jmc891205  
       2018-09-24 00:36:45 +08:00   1
    @ngg0707 对你给的这个例子来说是的 你不声明 const int&a 和 int &&a 的话 那是不会给常量 1 分配内存
    anonymous256
        5
    anonymous256  
       2018-09-24 00:38:03 +08:00 via Android   2
    对于 1,2.55 ,'a',这类的数据,它们是存放在寄存器上的,用于初始化等操作。没有所谓的内存地址,属于字面常量(literal constant)。如 int x = 5,x 是左值,5 是右值。x 是变量(左值)有地址,5 是字面常量(右值)无地址。

    C/C++能凡是能取到地址的,该类被认为是左值。字面值常量被认为是右值,不允许取地址。

    至于楼主提到的 const 常量,const 只是修饰符,只能说是 const 修饰后,该变量不能被修改,它和字面值常量(右值)有着本质的不同。

    我了解的是这样了,欢迎指正。
    ngg0707
        6
    ngg0707  
    OP
       2018-09-24 00:40:20 +08:00
    @anonymous256 我觉得楼上解决了我的问题。你可以看看楼上的回答。
    snnn
        7
    snnn  
       2018-09-24 00:47:34 +08:00
    @anonymous256 完全错误!!!
    jmc891205
        8
    jmc891205  
       2018-09-24 01:04:01 +08:00
    @jmc891205 我在 4 楼有地方说的可能有歧义
    不是「不会给常量 1 分配内存」
    而是不会分配内存去存储常量 1
    429839446
        9
    429839446  
       2018-09-24 01:04:54 +08:00
    如果我没记错。
    常量引用可以绑定右值。
    常量引用本身是左值。
    Sparetire
        10
    Sparetire  
       2018-09-24 01:41:48 +08:00 via Android
    常量和变量不可变还是有区别的吧,我的理解是 const 还是运行时创建,而常量是编译时就确定的
    raysonx
        11
    raysonx  
       2018-09-24 02:00:02 +08:00 via iPad   1
    @anonymous256 然而你的理解是错误的。
    另外 @ngg0707
    其他架构的 cpu 我不敢说,在 x86 架构下,整数的右值通常直接对应汇编的立即数寻址,也就是直接保存在编译后的机器指令中,并不会被保存在寄存器。以你举的 int x=5 为例,编译后的汇编指令通常是这样的:

    mov [x 变量的内存地址], 5

    在实际的机器代码中就是保存在了二进制指令的几个字节中。在 cpu 执行到这一句的时候,动态载入到指定的内存地址(或寄存器)。

    对于字符串,极有可能是保存在一个单独的段中。
    xupefei
        12
    xupefei  
       2018-09-24 02:28:44 +08:00
    @raysonx #11 字符串赋值是是 mov dword ptr,本身存在可执行文件的 rdata 段里,通过 HEX 编辑器就能找到。
    shoujiaxin
        13
    shoujiaxin  
       2018-09-24 03:08:17 +08:00 via iPad
    C++ Primer (第五版)中文版的第 55 页有解释
    这是一种例外情况,初始化常量引用时允许使用任意表达式作为初始值!包括非常量的对象、字面值或者一般的表达式!只要该表达式的结果能转换成引用的类型即可。
    字面值的常量引用实际是绑定到一个临时量
    geelaw
        14
    geelaw  
       2018-09-24 03:51:37 +08:00 via iPhone
    那你觉得 int x; int const &y = x; 的话 &y 有没有意义呢?
    snnn
        15
    snnn  
       2018-09-24 05:20:23 +08:00
    这种垃圾代码你们纠结一半天有什么意义?现实工作中谁把代码写成这样先去好好反省下正确的该怎么写。
    mintist
        16
    mintist  
       2018-09-24 09:51:45 +08:00
    楼主,来看看汇编代码就晓得为啥还是能取到地址了,把代码再精简下,然后结合汇编来看下。


    精简的 C++代码:

    ```c
    int main(void)
    {

    const int &a = 1; // 将变量 a 指向常量的地址,后面使用时可直接引用使用


    return 0;
    }
    ```

    对应在 ARM-gcc 下的汇编代码:注释是后面添加的

    ```asm
    main:
    sub sp, sp, #8 ; 在函数内申请栈空间,通过偏移 sp 来实现

    mov r3, #1 ; 将立即数放到寄存器 r3 中
    str r3, [sp] ; 将 r3 的值推到栈中
    mov r3, sp ; 将 sp 值也就是栈地址保存到 r3 中,也就是放置立即数 1 的内存地址
    str r3, [sp, #4] ; 将放置立即数 1 的内存地址放在栈中(带偏置),也就是我们所需要的 a 变量的值,后面需要引用 a 所指向的值时,编译器就去这个地址取出来,再指过去就好了。

    ; ARM 体系结果默认把第 1 个返回值放到 r0 中,所以在 bx 之前把 r0 的值 0 准备好就可以了
    mov r3, #0
    mov r0, r3
    add sp, sp, #8
    bx lr
    ```

    所以,回到“为什么 const 引用可以指向常量还可以取到地址?”这个问题,在汇编代码看来,就是先把立即数 1 放到存储空间(这里是栈空间,如果是全局变量,那么就会在链接时到.rodata 段内存空间),然后再把 a 变量的本身也存下(存的值就是 1 的地址),用的时候取出来指过去就可以了。



    详见如下的链接: https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(j:1,lang:c%2B%2B,source:'int+main(void)%0A%7B%0A++++const+int+%26a+%3D+1%3B%0A++++return+0%3B%0A%7D%0A'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:arm710,filters:(b:'1',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'0',trim:'0'),lang:c%2B%2B,libs:!(),options:'-fomit-frame-pointer',source:1),l:'5',n:'0',o:'ARM+gcc+7.2.1+(none)+(Editor+%231,+Compiler+%231)+C%2B%2B',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4
    mintist
        17
    mintist  
       2018-09-24 09:54:43 +08:00
    @snnn 可能楼主和我一样,是给自己设计的芯片写代码,然后给别人用的,所以需要比较纠结这些细节,,,
    iwtbauh
        18
    iwtbauh  
       2018-09-24 09:57:47 +08:00 via Android
    @raysonx #11

    可能他说的是 RISC 架构的计算机吧,你说的 x86 这种 CISC 计算机自然不一样。

    int x = 5,在 RISC 上是这样工作的

    mov 5, 寄存器 1
    store 寄存器 1, (寄存器 2)

    确实需要“放在寄存器”上

    @anonymous256 #5

    但是说法非常有歧义且容易混淆,立即常量是先在机器指令里,再在寄存器里。
    iwtbauh
        19
    iwtbauh  
       2018-09-24 10:05:23 +08:00 via Android
    再回到 lz 的问题

    为什么会有地址,其实编译器也很无奈啊,本来设计的当的编译器不会给 const 分配地址,让他做立即常量来避免多一次的内存访问(要知道访问内存可是开销很大的操作呀),但是 lz 偏偏要 &const,这相当于创建 const 变量的一个引用,于是编译器就不能把它优化掉了,所以 lz 才看到地址输出
    wizardforcel
        20
    wizardforcel  
       2018-09-24 10:29:14 +08:00
    const 那种东西,你把它当成只读变量就好了。。
    wizardforcel
        21
    wizardforcel  
       2018-09-24 10:29:33 +08:00
    @jmc891205 `a + 1` 也是临时变量,但它没有地址。
    pagict
        22
    pagict  
       2018-09-24 15:04:00 +08:00 via iPhone
    @snnn 单这个例子的代码是没啥好探讨的垃圾代码,但以此可以摸索编译器的内在原理,也不失为一种有意义的讨论
    wizardforcel
        23
    wizardforcel  
       2018-09-24 15:29:15 +08:00 via Android
    #20 #21 原来是 const& 啊,我看错了。。。
    sfqtsh
        24
    sfqtsh  
       2018-09-24 15:54:13 +08:00 via Android
    右值引用和 const 左值引用都可以指向一个左值。引用本身是右值,而右值可以被取地址。
    kingcc
        25
    kingcc  
       2018-09-24 19:08:26 +08:00
    学习到了
    agagega
        26
    agagega  
       2018-09-24 20:09:44 +08:00 via iPhone
    特殊操作
    onemoo
        27
    onemoo  
       2018-09-24 21:00:25 +08:00
    只从语法角度考虑这个问题:
    你知道等号右侧的字面量 1 是右值。你也知道“ const 引用”是可以引用右值的。

    @sfqtsh 但是“取地址&运算符”的操作数需要是左值。

    记得 C++ 中有这样一条规则“具名引用为左值”。所以即便这个 a 是右值引用(&&),a 也被视为左值表达式,可以对其应用 & 运算符。
    ngg0707
        28
    ngg0707  
    OP
       2018-09-24 21:01:01 +08:00
    @mintist 应聘做题做到这个,就想了解一下……
    sfqtsh
        29
    sfqtsh  
       2018-09-24 21:36:44 +08:00 via Android
    @onemoo 衰,,,写错了。应该是:

    "右值引用和 const 左值引用都可以指向一个右值。引用本身是左值,而左值可以被取地址。"
    iceheart
        30
    iceheart  
       2018-09-24 22:06:28 +08:00 via Android
    都引用了,咋能没有地址呢?
    SKull4
        31
    SKull4  
       2018-09-24 22:27:24 +08:00
    @ngg0707 应聘的 iOS 开发么。。。
    dangyuluo
        32
    dangyuluo  
       2018-09-25 01:46:24 +08:00
    @anonymous256 大哥你是认真的么。。一本正经的胡说八道
    dangyuluo
        33
    dangyuluo  
       2018-09-25 01:49:20 +08:00
    xuanbg
        34
    xuanbg  
       2018-09-25 06:26:42 +08:00
    变量怎么能没有地址?
    innoink
        35
    innoink  
       2018-09-25 07:47:51 +08:00 via Android
    @xuanbg int a = 2; int& b = a; 那么&b 是 a 的地址还是 b 的地址?
    xuanbg
        36
    xuanbg  
       2018-09-25 08:50:28 +08:00
    @innoink &b 是变量 b 的指针,也就是变量 b 在栈上的地址,b 才是指向 a 的地址
    wutiantong
        37
    wutiantong  
       2018-09-25 10:16:50 +08:00   1
    liuminghao233
        38
    liuminghao233  
       2018-09-25 13:30:11 +08:00
    @xuanbg
    int a = 2;
    int& b = a;
    这样的话
    &a == &b
    innoink
        39
    innoink  
       2018-09-25 14:00:46 +08:00
    @xuanbg 你错了,int&b = a;在这一刻以后 b 和 a 完全等价,包括&b 其实就是&a,所以楼主才有"如何对字面量取地址"的疑问。
    innoink
        40
    innoink  
       2018-09-25 14:01:49 +08:00
    @xuanbg 我估计你搞混了引用类型和指针类型。你无法对一个引用类型的变量取地址。
    cyspy
        41
    cyspy  
       2018-09-25 14:56:11 +08:00
    虽然不懂 C++,真的没人考虑这是不是 UB 吗。。
    ngg0707
        42
    ngg0707  
    OP
       2018-09-25 16:41:54 +08:00
    @cyspy 但是特么考试考啊
    across
        43
    across  
       2018-09-27 10:25:47 +08:00   1
    more effective C++似乎讲过。临时值生成的那节。

    const int& a = 1;属于 ref to const,目标值为常量,允许。
    int& a = 1,ref to non-const,目标值可能被修改,不允许。
    FrankHB
        44
    FrankHB  
       2018-10-05 11:58:39 +08:00
    @xuanbg 初始化一个函数引用,你告诉我这货地址是啥?
    @cyspy 不是 UB。
    const lvalue reference 用 prvalue 初始化类型兼容,temporary materialization conversion 过了自然就有个对象,但是抽象机语义上有对象和实现是不是给对象分配存储是两回事。就算分配,这种没 ABI 限制的东西不放寄存器里要放主存可以算是没事找事。
    至于&的结果还真不一定,因为<<出来的内容是 implementation-defined,编译器完全可以不生成一个对象给你随便一个幺蛾子,只要给文档就行。只不过通常实现都懒得和 iostream 这种破烂有一腿日 builtin 所以看不到而已……
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1251 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 87ms UTC 17:20 PVG 01:20 LAX 09:20 JFK 12:20
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86