对变量的改变赋值我有一点疑问


#1

比如说声明一个变量 a =1
然后改变他 a=2
这个时候我想问的是程序是在a原有的内存地址上修改,还是像纯函数使语言那样生成一个1的值然后让a这个标识指向这个新创建的值的地址?


#2

immutable类型没有稳定内存地址,这是语言的要求
具体的实现上可能只是修改栈上的一个变量,它是有内存地址的
但你不能依赖这种未定义行为,写过C++的都知道我在说什么

不要混淆语言规范与实现


#3

可以看看源码的 : 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就不知道是什么意思了)。

楼上说的对:不能依赖实现。


#4

应该是区分局部变量和全局变量的

倒也不是局部和全局的问题。

不要依赖未定义行为的根本原因是,语言只需提供抽象(以降低使用者的心智负担),保证已定义行为的正确性,它在实现层面有足够的自由度作出各种优化措施;而这些实现手段就是未定义行为。一旦我们依赖未定义行为,就打破了我们向语言保证的承诺,真的会出事…

举个最简单的例子就是可变性。Julia规定不可变的对象(比如楼主的Int(1))没有稳定的内存地址,并且它自身的内容不随时间变化。那么:

  1. 实现可以在内存中任意移动不可变的对象
  2. 对该对象的后续引用都可以内联成常量

所以不可变的对象具有更高的性能。另外,因为没有稳定内存地址,所以比较两个不可变的对象的唯一依据就是它们的值,所以值一样的不可变对象总是相等的,无法使用语言手段区分它们。

举个例子:

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调用的参数都是立即数。
当然,有的时候不可变对象也会保存在栈上,此时它是有内存地址的,但是你还是不应该尝试拿到它的内存地址甚至修改它。