#include <iostream>
using namespace std;
int main(int argc, char const* argv[])
{
const int& a = 1;
cout << &a << endl;
return 0;
}
不知道该怎么搜索这个问题,没查到。有人知道吗?
1
jmc891205 2018-09-24 00:23:41 +08:00
编译器生成了一个临时对象
|
2
ngg0707 OP ```cpp
#include <iostream> using namespace std; int main(int argc, char const* argv[]) { int&& a = 1; cout << &a << endl; return 0; } ``` 这样居然也可以。 |
4
jmc891205 2018-09-24 00:36:45 +08:00 1
@ngg0707 对你给的这个例子来说是的 你不声明 const int&a 和 int &&a 的话 那是不会给常量 1 分配内存
|
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 修饰后,该变量不能被修改,它和字面值常量(右值)有着本质的不同。 我了解的是这样了,欢迎指正。 |
6
ngg0707 OP @anonymous256 我觉得楼上解决了我的问题。你可以看看楼上的回答。
|
7
snnn 2018-09-24 00:47:34 +08:00
@anonymous256 完全错误!!!
|
9
429839446 2018-09-24 01:04:54 +08:00
如果我没记错。
常量引用可以绑定右值。 常量引用本身是左值。 |
10
Sparetire 2018-09-24 01:41:48 +08:00 via Android
常量和变量不可变还是有区别的吧,我的理解是 const 还是运行时创建,而常量是编译时就确定的
|
11
raysonx 2018-09-24 02:00:02 +08:00 via iPad 1
@anonymous256 然而你的理解是错误的。
另外 @ngg0707 其他架构的 cpu 我不敢说,在 x86 架构下,整数的右值通常直接对应汇编的立即数寻址,也就是直接保存在编译后的机器指令中,并不会被保存在寄存器。以你举的 int x=5 为例,编译后的汇编指令通常是这样的: mov [x 变量的内存地址], 5 在实际的机器代码中就是保存在了二进制指令的几个字节中。在 cpu 执行到这一句的时候,动态载入到指定的内存地址(或寄存器)。 对于字符串,极有可能是保存在一个单独的段中。 |
12
xupefei 2018-09-24 02:28:44 +08:00
@raysonx #11 字符串赋值是是 mov dword ptr,本身存在可执行文件的 rdata 段里,通过 HEX 编辑器就能找到。
|
13
shoujiaxin 2018-09-24 03:08:17 +08:00 via iPad
C++ Primer (第五版)中文版的第 55 页有解释
这是一种例外情况,初始化常量引用时允许使用任意表达式作为初始值!包括非常量的对象、字面值或者一般的表达式!只要该表达式的结果能转换成引用的类型即可。 字面值的常量引用实际是绑定到一个临时量 |
14
geelaw 2018-09-24 03:51:37 +08:00 via iPhone
那你觉得 int x; int const &y = x; 的话 &y 有没有意义呢?
|
15
snnn 2018-09-24 05:20:23 +08:00
这种垃圾代码你们纠结一半天有什么意义?现实工作中谁把代码写成这样先去好好反省下正确的该怎么写。
|
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 |
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 但是说法非常有歧义且容易混淆,立即常量是先在机器指令里,再在寄存器里。 |
19
iwtbauh 2018-09-24 10:05:23 +08:00 via Android
再回到 lz 的问题
为什么会有地址,其实编译器也很无奈啊,本来设计的当的编译器不会给 const 分配地址,让他做立即常量来避免多一次的内存访问(要知道访问内存可是开销很大的操作呀),但是 lz 偏偏要 &const,这相当于创建 const 变量的一个引用,于是编译器就不能把它优化掉了,所以 lz 才看到地址输出 |
20
wizardforcel 2018-09-24 10:29:14 +08:00
const 那种东西,你把它当成只读变量就好了。。
|
21
wizardforcel 2018-09-24 10:29:33 +08:00
@jmc891205 `a + 1` 也是临时变量,但它没有地址。
|
22
pagict 2018-09-24 15:04:00 +08:00 via iPhone
@snnn 单这个例子的代码是没啥好探讨的垃圾代码,但以此可以摸索编译器的内在原理,也不失为一种有意义的讨论
|
23
wizardforcel 2018-09-24 15:29:15 +08:00 via Android
#20 #21 原来是 const& 啊,我看错了。。。
|
24
sfqtsh 2018-09-24 15:54:13 +08:00 via Android
右值引用和 const 左值引用都可以指向一个左值。引用本身是右值,而右值可以被取地址。
|
25
kingcc 2018-09-24 19:08:26 +08:00
学习到了
|
26
agagega 2018-09-24 20:09:44 +08:00 via iPhone
特殊操作
|
27
onemoo 2018-09-24 21:00:25 +08:00
只从语法角度考虑这个问题:
你知道等号右侧的字面量 1 是右值。你也知道“ const 引用”是可以引用右值的。 @sfqtsh 但是“取地址&运算符”的操作数需要是左值。 记得 C++ 中有这样一条规则“具名引用为左值”。所以即便这个 a 是右值引用(&&),a 也被视为左值表达式,可以对其应用 & 运算符。 |
29
sfqtsh 2018-09-24 21:36:44 +08:00 via Android
|
30
iceheart 2018-09-24 22:06:28 +08:00 via Android
都引用了,咋能没有地址呢?
|
32
dangyuluo 2018-09-25 01:46:24 +08:00
@anonymous256 大哥你是认真的么。。一本正经的胡说八道
|
33
dangyuluo 2018-09-25 01:49:20 +08:00
Stackover 上有一个回答讲的非常好:
https://stackoverflow.com/questions/2088259/literal-initialization-for-const-references |
34
xuanbg 2018-09-25 06:26:42 +08:00
变量怎么能没有地址?
|
35
innoink 2018-09-25 07:47:51 +08:00 via Android
@xuanbg int a = 2; int& b = a; 那么&b 是 a 的地址还是 b 的地址?
|
37
wutiantong 2018-09-25 10:16:50 +08:00 1
|
38
liuminghao233 2018-09-25 13:30:11 +08:00
|
39
innoink 2018-09-25 14:00:46 +08:00
@xuanbg 你错了,int&b = a;在这一刻以后 b 和 a 完全等价,包括&b 其实就是&a,所以楼主才有"如何对字面量取地址"的疑问。
|
41
cyspy 2018-09-25 14:56:11 +08:00
虽然不懂 C++,真的没人考虑这是不是 UB 吗。。
|
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,目标值可能被修改,不允许。 |
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 所以看不到而已…… |