如果我们有
int a = 123;
int b = 123;
Run Code Online (Sandbox Code Playgroud)
我们最终会为整数分配两个不同的内存块123,还是最终只为123和 变量分配一个内存块a,并且b只是加载到相同的内存地址?
关于什么
int a = 123;
int b = a;
Run Code Online (Sandbox Code Playgroud)
这会改变答案吗?
我尝试打印出C++中两个变量的内存地址,发现它们是不同的
int a = 123;
cout << &a << endl; // 0x7fff46512da0
int b = 123;
cout << &b << endl; // 0x7fff46512da4
Run Code Online (Sandbox Code Playgroud)
这是否意味着在该特定环境中程序int 123在两个不同的内存块中存储重复项?
如果值是字符串,答案会改变吗?
我问这个问题的原因是我发现在Python中,如果原始值相等,那么内存地址总是相同的。我听说是因为常量池。我想知道这是否仍然适用于 C 和 C++?
例如
a = 123
b = 123
print(id(a)) // 9792896
print(id(b)) // 9792896
Run Code Online (Sandbox Code Playgroud)
Eri*_*hil 13
C 和 C++ 程序具有双重性质。程序的含义是使用理论模型和抽象计算机来描述的,该计算机按源描述的字面意思执行程序。在此模型中,每个对象都具有与其他对象不同的内存,因为根据定义,对象是与类型关联的保留内存。(请注意,源代码中的字符串文字可能会重叠,引用一个公共数组。)
但是,编译器不需要生成按字面意思执行此含义的汇编代码。它可能会生成与原始源代码具有相同可观察行为的任何程序。可观察的行为包括程序写入文件的输出、输入/输出交互以及对易失性对象的访问。在可观察的行为之间,编译器可以优化程序,包括消除不必要的内存使用。
每当您定义一个对象时,如果编译器能够使您的程序在不使用此类内存的情况下运行,则它可能根本不会为其保留内存。例如,在:
int main(void)
{
int a = 123;
int b;
scanf("%d", &b);
printf("%d\n", a+b);
}
Run Code Online (Sandbox Code Playgroud)
编译器可能通过加载常量 123 作为指令的立即操作来执行计算,而不为其保留单独的内存。
如果编译器确实需要内存,可能是因为它没有足够的处理器寄存器来将其正在使用的所有内容保存在寄存器中,那么它可能只保留常量的一份副本,该常量用于初始化两个从未更改且其地址的对象没有被采取。
如果通过地址将对象传递给其他例程或给它们不同的值,则编译器更有可能根据情况为它们保留单独的内存。
埃里克的回答非常好。我将添加一些使用 C 作为基本语言的实际案例来回答。
采取以下代码:
#include <stdio.h>
int main() {
int a = 123;
int b = 123;
printf("%d", a);
printf("%d", b);
}
Run Code Online (Sandbox Code Playgroud)
如果使用 gcc 11.2 x86-64 C 编译器 (intel asm) 编译此代码,则会生成以下程序集:
.LC0:
.string "%d"
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 123
mov DWORD PTR [rbp-8], 123
mov eax, DWORD PTR [rbp-4]
mov esi, eax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
mov eax, DWORD PTR [rbp-8]
mov esi, eax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
mov eax, 0
leave
ret
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,为这两个变量提供了存储。
现在,如果我使用优化-O标志,则会生成以下程序集:
.LC0:
.string "%d"
main:
sub rsp, 8
mov esi, 123
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
mov esi, 123
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
mov eax, 0
add rsp, 8
ret
Run Code Online (Sandbox Code Playgroud)
编译器只使用123文字,因为没有对这些变量进行任何更改,它认为它们可以被视为常量值并且不需要存储。
这并不意味着文字存在于以太中,它必须嵌入到程序集中。
使用Python,一切都是对象,甚至是原始类型,请注意,print(id(a))和print(id(123))都会呈现相同的结果,在这两种情况下,特定对象的标识符123、指向它的指针或引用(如果您愿意的话),但与它所属的变量没有任何关系分配的。
另一方面,C/C++ 与 Python 不同,int文字不是对象,没有对它们的引用,只是位。对于123字面示例,让我们尝试打印它的地址:
printf("%p\n", (void*)123);
Run Code Online (Sandbox Code Playgroud)
这里发生了什么:
mov esi, 123 // sets ESI register to 123
mov edi, OFFSET FLAT:.LC0 //unimportant, gets the specifier string
mov eax, 0 // sets EAX register to 0
call printf // prints the literal
Run Code Online (Sandbox Code Playgroud)
输出:
0x7b // 123 hexadecimal
Run Code Online (Sandbox Code Playgroud)
现在我们还打印已分配的变量的地址123:
int a = 123;
printf("%p", (void*)&a);
Run Code Online (Sandbox Code Playgroud)
查看装配体,我们可以发现差异:
mov DWORD PTR [rsp+12], 123 // moving `123` literal to its address
lea rsi, [rsp+12] // placing the address in the register
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf // printing the address
Run Code Online (Sandbox Code Playgroud)
在这种情况下,如预期的那样打印变量的地址。文字被放置在变量所在的内存位置a,因此我们可以打印它的地址。
如果你有两个具有相同值的变量,它们可能会有不同的地址,但是如果编译器找到一种方法让这两个变量只有一个地址或根本没有内存存储,并且仍然产生所需的结果,没有任何规则可以阻止它。
语言标准对编译器的功能几乎没有任何限制,它只需符合语言标准规则并生成一个在所有情况下都以一致的、定义的方式运行的程序,只要它编码正确即可。
ato的赋值b本身不会改变太多,而且文字是字符串的事实也不会改变,同一个文字可能只有一个副本(特别是考虑到通过分配给指针创建的字符串文字是不可变的),除非还有其他限制因素阻止它。
边注:
C 和 C++ 是不同的语言,我想明确指出这一点,因为 C++ 通常被错误地视为 C 的超集,尽管早年可能是这样,但今天情况并非如此,这些都是非常严重的。不同的语言,尽管 C++ 在很大程度上保留了 C 代码的兼容性。
我们最终会分配两个不同的内存块吗
就抽象机而言:是的,变量具有重叠的存储持续时间,因此它们必须具有不同的内存地址。
就语言实现而言:这取决于。如果不需要的话,甚至可以根本不使用内存。
这会改变答案吗?
不。