Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[第五章] 诸多名词概念不应强行关联 #77

Closed
kvinwang opened this issue Jan 15, 2019 · 19 comments
Closed

[第五章] 诸多名词概念不应强行关联 #77

kvinwang opened this issue Jan 15, 2019 · 19 comments
Labels
已修订 已经修订并提交给出版社 第五章 第五章 精选 表意问题 语言表达有问题,有歧义或难理解 读者建议 来自读者对本书的建议

Comments

@kvinwang
Copy link

kvinwang commented Jan 15, 2019

第五章里面将“按位复制”、“栈复制”、“浅复制/深复制”、“值语义/引用语义”等概念强行关联起来,实际上这些关联关系并不成立。

例如:

  • 第120页

值类型是指直接存储在栈中的数据类型

值类型判断标准和赋值语句时的复制语义有关,和是否存储在栈上无关。

引用类型将数据存放在堆中,而栈上只存放指向堆中数据的指针。

问题同上( 数据也可以放在栈上,指针也可以放到堆上)[1]

  • 第121页

值语义: 按位复制后,与原对象无关

值语义的复制不一定要按位复制。

引用语义: 也叫指针语义。... 堆内存中... 栈内存中

问题同[1]

按位复制就是指栈复制,也叫浅复制,它只复制栈上的数据。相对而言,深复制就是对栈和堆上的数据一起复制。

这里进一步错误地引入浅复制和深复制的概念。实际上,深/浅复制,和是否在栈上或是否是按位复制都没有关系。浅复制的数据完全可以在堆上,也可以不按位来复制, 深复制的数据也完全可以全部存在于栈上。

  • 第123页

在Rust中,由Copy trait来区分值语义和引用语义。

Copy语义和值语义是不同的概念。Rust中非Copy的但满足值语义的类型很常见。


实际上, 这些概念都有自己的定义,分开理解就好,不应该强行关联。

@ZhangHanDong
Copy link
Owner

ZhangHanDong commented Jan 15, 2019

@kvinwang 感谢反馈。这是第三次印刷要修订的内容。

在不同的语言概念里,值语义应该有不同的理解。其实放到Rust语言里,Copy就是判断Rust中值语义类型的一种依据。

@ZhangHanDong ZhangHanDong added this to the 第三次印刷 milestone Jan 15, 2019
@ZhangHanDong ZhangHanDong added 表意问题 语言表达有问题,有歧义或难理解 第五章 第五章 读者建议 来自读者对本书的建议 待确认 待确认的问题 labels Jan 15, 2019
@kvinwang
Copy link
Author

kvinwang commented Jan 15, 2019

在不同的语言概念里,值语义应该有不同的理解。其实放到Rust语言里,Copy就是判断Rust中值语义类型的一种依据。

我见过的有官方明确定义或解释值语义的语言有C++/Swift/Nim,他们对值语义的定义都是一致的。
Rust官方没有定义值语义,那么本书要引用该名词就应该按其他语言的通行标准来,不应在本书发明一种新的不兼容的定义。

@ZhangHanDong
Copy link
Owner

@kvinwang 我已明白你意思。上面说的那些对应关系后面会修正的更加精准。

@ZhangHanDong ZhangHanDong removed the 待确认 待确认的问题 label Jan 15, 2019
@ZhangHanDong
Copy link
Owner

ZhangHanDong commented Jan 21, 2019

@kvinwang 审校下。

修正内容:

值类型一般是指可以将数据都保存到同一位置的类型,一些原生类型,比如数值、布尔值、结构体等都是值类型。因此对值类型的操作效率一般比较高,使用完立即会被回收。值类型作为右值(在值表达式中)执行赋值操作时,会自动复制(通常为浅复制)一个新的值副本。浅复制是指,只复制数据对象的值,但是其如果包含了引用的其他数据对象,则不会被复制。

引用类型则会存在一个指向实际存储区域的指针。比如将数据存储在堆中,而栈中只存放指向堆中数据的地址(指针)这样的类型。因此对引用类型的操作效率一般比较低,使用完交给GC回收,没有GC的语言则需要靠手工来回收。

基本的原生类型、结构体和枚举体都属于值类型。普通引用类型、原生指针类型等都属于引用类型。但随着语言的发展,类型越来越丰富,值类型和引用类型已经难以描述全部情况,比如一个Vector容器类型,其内部可以包含基本的值类型,也可以包含引用类型,那它属于什么类型?

为了更加精准地对这种情况进行描述,值语义(Value Semantic)和引用语义(Reference Semantic)被引入,定义如下。 �

  • 值语义:复制(赋值操作)以后,两个数据对象拥有的存储空间是独立的,相互之间互不影响。
  • 引用语义:复制(赋值)以后,两个数据对象,相互之间互为别名。操作其中任意一个数据对象,则会影响到另一个。

值语义可以保证变量值的独立性(Independence)。独立性的意思是,如果想修改某个变量,只能通过它本身来修改;而如果修改了它本身,并不影响其复制品。也就是说,如果只能通过变量本身来修改值,那么它就是具有值语义的变量。

对于引用语义的数据对象,赋值操作的浅复制,可能存在内存不安全风险。比如只复制了栈上的指针,堆上的数据就多了一个管理者,多了一层内存安全的隐患。图5-1所示是原生整数类型和引用类型按位复制的示意图。

@ZhangHanDong ZhangHanDong added the 已修订 已经修订并提交给出版社 label Jan 21, 2019
@ZhangHanDong
Copy link
Owner

@mzji 也帮忙审校下上面的修正

@mzji
Copy link

mzji commented Jan 21, 2019

Rust 里,除了引用以外并没有真正意义上的引用类型吧……

@kvinwang
Copy link
Author

值语义&引用语义的理解

值语义和引用语义是描述一个类型在“赋值”或传参时的行为是否复制了该类型描述的“值”。
所以需要结合该类型的值的含义来理解。在Swift中,String,Array,Dictionary这些动态大小的类型都是值语义的,因为语义上它们在“赋值”时会连同堆上的内容被拷贝成两份 (虽然从编译优化的层面会尽力减少不必要的实际拷贝,语义上仍然是拷贝的)。
那么我们以C语言为例:

// 基本类型是值语义
int a = 1;
int b;
b = a; // 将int代表的值1从a拷贝到了b里面。

// 指针也是值语义,因为字面意义上指针的值就是地址
const char* a = "hi";
const char* b;
b = a; // 将"地址"从a拷贝到了b里面。

// 重新定义的指针, 引用语义
typedef const char* str; // 现在str代表字符串了,意义上不在是“针”
str a = "hi";
str b;

b = a; // str描述的值-字符串,没有被拷贝, 因此是引用语义。

内存表示上完全相同的char*str, 本质上都是指针,但语义上一个是值语义一个是引用语义。
判断时关注重点要放在类型所描述的“值”的含义。

@kvinwang
Copy link
Author

浅拷贝&深拷贝的理解

浅拷贝和深拷贝,这个深浅是相对的,只能有框架性的定义,不能从技术上对所有类型的拷贝细节进行统一规定。每个类型都可以定义自己何为深、何为浅。正如我们小学课文《小马过河》所学,松鼠会觉得河水达到10CM就是深,但对于老牛来讲100CM也不算深。

以Python的list举例说明:

In [1]: import copy

In [2]: a = [[], []]                       # 先创建一个含有list的list对象

In [3]: b = a                               # 不拷贝(Python里面所有类型都是引用语义)

In [4]: c = copy.copy(a)            # 浅拷贝

In [5]: d = copy.deepcopy(a)    #  深拷贝

In [6]: a.append(0)                   #  修改原始数据第一维

In [7]: a
Out[7]: [[], [], 0]

In [8]: b
Out[8]: [[], [], 0]

In [9]: c                        # c第一维没被修改,说明浅拷贝拷贝了第一维
Out[9]: [[], []]

In [10]: d                      # 深拷贝自然也会拷贝第一维
Out[10]: [[], []]

In [11]: a[0].append(1)    # 现在修改第二维
In [12]: a
Out[12]: [[1], [], 0]

In [13]: b
Out[13]: [[1], [], 0]

In [14]: c                         # 浅拷贝没有拷贝第二维
Out[14]: [[1], []]

In [15]: d                         # 深拷贝拷贝了第二维
Out[15]: [[], []]

实际上,在Python的标准库中,对深拷贝和浅拷贝进行了框架性规定。区别就是浅拷贝只在该类型上执行一次该类型定义的浅拷贝,深拷贝则会递归地进行深拷贝,至于如何递归,也是由该类型自己定义的。
参考:https://docs.python.org/3.7/library/copy.html

@kvinwang
Copy link
Author

kvinwang commented Jan 22, 2019

按位复制的理解

很简单,按位复制就是据需要复制数据的大小按每bit原样复制。与是否在栈上无关。

#[derive(Copy, Clone)]
struct A(i8, i32);
fn main() {
    let a = A(1, 2);
    let b = a; // 按位复制
    let c = A(a.0, a.1); // 逐成员复制,非按位复制
    // 注意二者的区别:按位复制会连padding部分一起复制。
    // 复制后,b和a完全相同,包括padding部分。c和a的padding部分不一定相同。
}

@ZhangHanDong
Copy link
Owner

@kvinwang 辛苦了。

我再修正一下描述。

@mzji
Copy link

mzji commented Jan 22, 2019

这么说的话,Rust 里的引用类型除了引用外,主要就是那些实现了 deref/deref_mut 的类型吧

@mzji
Copy link

mzji commented Jan 22, 2019

另,建议 #75#77 合并讨论……

@ZhangHanDong
Copy link
Owner

@mzji 回头我整理个单独贴,其实这个话题已经很清晰了,我明天整理出结果,重开个issues。

@kvinwang
Copy link
Author

这么说的话,Rust 里的引用类型除了引用外,主要就是那些实现了 deref/deref_mut 的类型吧

要看具体实现吧,deref也可以实现成值语义的

另,建议 #75#77 合并讨论……

本来两个issue提的不同的问题,讨论的时候联系到一块了。

@mzji
Copy link

mzji commented Jan 23, 2019

Implementing Deref for smart pointers makes accessing the data behind them convenient, which is why they implement Deref. On the other hand, the rules regarding Deref and DerefMut were designed specifically to accommodate smart pointers. Because of this, Deref should only be implemented for smart pointers to avoid confusion.

考虑到这一点, deref / deref_mut 不应被实现为值语义。

@kvinwang
Copy link
Author

kvinwang commented Jan 23, 2019

考虑到这一点, deref / deref_mut 不应被实现为值语义。

应该当然是应该,字面上就能看出来应该实现为引用语义。还是那句话,should不能被当做must,不能把should的东西当成判别标准,否则就变成错误了,尤其是写到书上就更需要严谨了。

Edit:
Rust标准库里面也能找到实现了Deref还满足值语义的例子,如:ManuallyDrop

@ZhangHanDong
Copy link
Owner

@mzji
Copy link

mzji commented Jan 23, 2019

Rust标准库里面也能找到实现了Deref还满足值语义的例子,如:ManuallyDrop

那只好再精确一点,引用和智能指针

@ZhangHanDong
Copy link
Owner

万事总能找出特例的。只要没有太大的错误,读者方便理解,暂时就不想再深抠下去了。书是没办法讲那么细致的。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
已修订 已经修订并提交给出版社 第五章 第五章 精选 表意问题 语言表达有问题,有歧义或难理解 读者建议 来自读者对本书的建议
Projects
None yet
Development

No branches or pull requests

3 participants