跳到主要内容

传值和传引用

· 阅读需 8 分钟

    在现在常用的文本编程语言(C++, Java, C#)中,调用子函数时的传参方式主要是传引用方式,就是说,告诉被调用的函数的是参数所在的位置,而不是参数的数据。C++ 为了保持和 C 语言的兼容,一般的简单数据还是使用值传递,但对于大块的数据,比如数组,字符串,结构,类等等,也基本上都是引用形式传递的。
    值传递的方式的缺点是显而易见的:每次调用子函数的时候,需要把数据拷贝一份,耗费大量的内存。传引用的方式,不需要每次都拷贝数据,节省了内存空间,和复制数据的时间。但是传引用的安全性不如直接传值,因为传引用的时候,数据所在的内存也可以被其它函数访问,这在单线程下,问题不大。但是多线程下,就不能保证数据的安全了。

    理论上,一个数据流驱动的编程语言,可以只采用值传递。数据在每一个联线分叉的地方,都做一个拷贝。这样任何一个节点所处理的数据都是它专用的,不需要担心线程之间会相互影响。在设计 LabVIEW 程序时,可以假设 LabVIEW 就是这样子工作的。但是 LabVIEW 的实际工作情况比这要复杂些,它在不违背数据流原则的前提下,做了一些优化以避免过多的复制数据。

    在某些时候,一个节点得到了输入数据,LabVIEW 如果能够确认这个输入数据的内存肯定不会被其他部分的程序代码使用到,并且恰好节点的一个输出需要一块内存,LabVIEW 就不在为输出数据令开辟一块内存了,而是使用那个输入数据所在的内存。这叫做缓存重用
    这种行为实质上和传引用是一样的,告诉函数一个数据的地址,然后函数直接在这个地址上处理数据。LabVIEW 程序员是不能够直接设置某个参数是传值还是传引用的。到底采用那种传递方式,是由 LabVIEW 来决定的。LabVIEW 决定采用哪种参数传递方式的原则是:首先保证数据的安全,其次才估计效率。LabVIEW 并不能总是准确的判断出某段代码采用传引用的方式是否安全。LabVIEW 本着宁枉勿纵的原则,对凡是拿不准的地方一律不优化,全部采用传值的方式,多拷贝一份数据。
    虽然不能够直接设置某个参数是传值还是传引用的,但追求效率的的程序员,可以通过改变程序风格,来帮助 LabVIEW 准确判断出那些代码可以优化,无需拷贝数据,从而让自己编写出来的 LabVIEW 代码效率最高。比如,使用移位寄存器缓存重用结构告诉 LabVIEW 在某个地方使用传引用的方式。

    LabVIEW 中有些节点的输入输出数据类型完全不一样,比如数组索引节点,输入是一个数组和索引,输出是一个数组的元素。输入和输出的数据类型一般情况下完全不同,所以必须未输出数据新开辟一块内存,根本不可能做到缓存重用。有些节点,总是有相同类型的输入输出,比如加法节点,输出值的数据类型总是和其中一个输入同类型(fixed-point 数据类型是个例外)。LabVIEW 要考虑尽量在这些节点使用缓存重用。
    如果输入值是数组数据,它通过分叉的连线被同时输入到一个数组索引节点和一个加法节点。假设其它数据都已就绪,LabVIEW 作为数据流驱动的程序,理论上应该同时运行着两个节点。但实际上,为了内存优化,在类似的情况下,LabVIEW 总是运行不可能做缓存重用的节点(比如这里是数组索引节点),然后再运行可以做缓存重用的节点(加法节点)。
    原因是这样的:如果先运行加法节点或者同时运行两个节点,因为加法节点的输入数据所在的内存还要被数组索引节点读取,因而加法节点是不能够改变这块内存中的数据的,那么加法节点只好再为输入数据开辟一块新内存;相反,如果先运行完数组索引节点,在运行加法节点的时候,加法节点输入数据所在的内存就不会再被别的节点使用了,这是加法节点就可以放心的把输入数据放到这块内存里,做到缓存重用。

    LabVIEW 虽然不能设置数据传递给一个节点时,使用值传递还是引用传递,但是 LabVIEW 中有一类专门的“引用型控件”,用来保证大块的数据不被频繁复制,或者在不同的线程内对同一内存做数据操作。一般叫做 xxx refnum 的控件都属于之一类,他们所代表的数据(也可用于表示某个设备)是不随着数据线流动的。程序上的连线出现分叉,虽然 refnum 这个值本身可能会被复制,但它所指向的数据是不会被拷贝的。

    另外,如果一定要在 LabVIEW 代码中实现传引用,可以通过以下的方法:做一个全局数组变量,把数据存在数组里。VI 间传递的信息是数据在数组中的索引,一个表示序号整数值,就相当于是这块数据的引用。这样,所有对块数据的操作都是在同一内存中的,并且不同线程可以同时对这块内存做修改。

**相关文章:
**    我和 LabVIEW