比如说声明一个变量 a =1
然后改变他 a=2
这个时候我想问的是程序是在a原有的内存地址上修改,还是像纯函数使语言那样生成一个1的值然后让a这个标识指向这个新创建的值的地址?
immutable类型没有稳定内存地址,这是语言的要求
具体的实现上可能只是修改栈上的一个变量,它是有内存地址的
但你不能依赖这种未定义行为,写过C++的都知道我在说什么
不要混淆语言规范与实现
可以看看源码的 : interpreter.c 的第 639 行
else if (head == assign_sym) {
jl_value_t *lhs = jl_exprarg(stmt, 0);
jl_value_t *rhs = eval_value(jl_exprarg(stmt, 1), s);
if (jl_is_slot(lhs)) {
ssize_t n = jl_slot_number(lhs);
assert(n <= jl_source_nslots(s->src) && n > 0);
s->locals[n - 1] = rhs;
}
else {
jl_module_t *modu;
jl_sym_t *sym;
if (jl_is_globalref(lhs)) {
modu = jl_globalref_mod(lhs);
sym = jl_globalref_name(lhs);
}
else {
assert(jl_is_symbol(lhs));
modu = s->module;
sym = (jl_sym_t*)lhs;
}
JL_GC_PUSH1(&rhs);
jl_binding_t *b = jl_get_binding_wr(modu, sym, 1);
jl_checked_assignment(b, rhs);
JL_GC_POP();
}
}
应该是区分局部变量和全局变量的(jl_get_binding_wr就不知道是什么意思了)。
楼上说的对:不能依赖实现。
应该是区分局部变量和全局变量的
倒也不是局部和全局的问题。
不要依赖未定义行为的根本原因是,语言只需提供抽象(以降低使用者的心智负担),保证已定义行为的正确性,它在实现层面有足够的自由度作出各种优化措施;而这些实现手段就是未定义行为。一旦我们依赖未定义行为,就打破了我们向语言保证的承诺,真的会出事…
举个最简单的例子就是可变性。Julia规定不可变的对象(比如楼主的Int(1)
)没有稳定的内存地址,并且它自身的内容不随时间变化。那么:
- 实现可以在内存中任意移动不可变的对象
- 对该对象的后续引用都可以内联成常量
所以不可变的对象具有更高的性能。另外,因为没有稳定内存地址,所以比较两个不可变的对象的唯一依据就是它们的值,所以值一样的不可变对象总是相等的,无法使用语言手段区分它们。
举个例子:
julia> function f()
a=1
println(a)
a=2
println(a)
end
f (generic function with 1 method)
julia> f()
1
2
这样的代码编译生成的LLVM指令其实是这样的:
define nonnull %jl_value_t addrspace(10)* @japi1_f_15952(%jl_value_t addrspace(10)*, %jl_value_t addrspace(10)**, i32) #0 {
top:
%3 = alloca %jl_value_t addrspace(10)**, align 8
store volatile %jl_value_t addrspace(10)** %1, %jl_value_t addrspace(10)*** %3, align 8
call void @julia_println_15953(i64 1)
call void @julia_println_15953(i64 2)
ret %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 4708622344 to %jl_value_t*) to %jl_value_t addrspace(10)*)
}
a根本就没有内存地址,两次println调用的参数都是立即数。
当然,有的时候不可变对象也会保存在栈上,此时它是有内存地址的,但是你还是不应该尝试拿到它的内存地址甚至修改它。