在 Pascal 中通过引用传递

Pass by reference in Pascal

我不确定我是否正确理解了 Pascal 中引用传递的工作原理。它是像在 C++ 中那样创建一个别名 (https://isocpp.org/wiki/faq/references),还是像在 C 中那样工作并且过程获取指向变量的指针并使用该指针。

我想我可以将我的问题表述为:Pascal 是否支持真正的引用传递,还是通过共享调用来完成。

例如 FreePascal 参考声明,该过程获取一个指针(https://www.freepascal.org/docs-html/current/ref/refsu65.html), but according to https://swinbrain.ict.swin.edu.au/wiki/Pass_by_Value_vs._Pass_by_Reference#Conclusion and for example https://cgi.csc.liv.ac.uk/~frans/OldLectures/2CS45/paramPassing/paramPassing.html#callByReference 在 Pascal 中通过引用传递的方式与在 C 中不同(在 C 中传递指针)。

如果有人能解释一下差异或引用传递的含义如何变化(在现代语言中我们说引用传递,但实际上它们是按值传递,例如 Java).那么按引用传递的原始含义是什么,它是如何工作的呢? Pascal 中的情况如何?

非常感谢。

按引用传递

in Delphi,按引用传递(使用varout)表示传递一个指针。但是要注意语义"pass by value"或"pass by reference"和实际传递是有区别的

var 或 out

与 C 的区别不在于实际传递(通过指针),而在于这些关键字的含义。 Var 只是通过引用传递。 Out 以不同方式处理某些托管类型,因为使用 out,您告诉编译器输入值不能保证被初始化。否则,这些在技术上是相同的(但在语义上不同):传递(IOW,指向)原始值的地址。

语义<>实际传递

语义

  • 按值传递的语义是例程获取原始值的本地副本,并且例程可以(如果允许)在不影响原件的情况下更改它。更改应用于本地副本并在例程结束时消失。
  • 通过引用传递的语义是您正在访问(并且可能修改)原始

实际低水平传球可能不同

但出于优化原因,实际生成的代码可能有所不同。特别是较大的结构(多大取决于版本和平台——这在某处有记录并且随着时间的推移而改变)通常通过引用通过(如指针),即使语义说 按值传递 。在例程的序言中(在第一个 begin 之前运行的隐藏代码),这些结构然后被 复制 到例程的本地框架。 请注意,并非所有调用约定都这样做。有时在调用之前对参数堆栈进行了完整复制,并且没有传递指针

由于你还有一个本地副本,并且不修改原件,这仍然被认为是按值传递,只是就像你宣布的那样。所有技术上的东西对用户来说都是透明的,所以区别只是在较低的层次上。这仅在您编写汇编程序或必须读取 CPU window.

中的生成代码时才重要

常量

如果声明了值参数const,原始值无论如何都不会被修改,因此可以省略将大型结构复制到本地框架并且可以通过引用访问(只读)值(指针),即使 语义 按值传递 。在这种情况下,小值总是按值传递(在寄存器中或堆栈中)。

虽然将 const var 作为参数修饰符没有意义(某些东西要么是常量要么是变量,但不能同时是两者),您仍然可以强制通过引用传递 const参数,通过使用 [ref] 修饰符属性:const [ref] X: Integer[ref] const X: Integer。整数通常按值传递(在寄存器中或堆栈中,具体取决于调用约定和平台)。

引用类型

请注意,如果您按值传递引用类型(例如对象、动态数组等),按值传递语义仅适用于引用 自己,即你得到了它们的副本,你可以修改 reference 而不会影响原件。

但是:这些引用指向的项(对象、数组等)可以被修改。只有 references 本身是本地副本,而不是它们指向的内容。因此,如果您在按值传递的对象引用上调用方法,那么该对象(在堆上)可能会被修改!这不是因为按值传递或按常量传递不能正常工作,而是因为引用是什么。

结论

  • 按值传递意味着对原件进行复制,并且可以在本地修改该副本。
  • 按引用传递意味着传递对原始文件的引用(指针),任何修改都会修改原始文件
  • 在幕后,技术上,在按值传递语义中,毕竟可以传递指针。如果有必要,会制作一份原件的副本,所以毕竟没有修改原件。
  • 按值传递引用类型,甚至像 const 一样传递引用类型不是意味着它们指向的原始(堆)项目防止被修改.没有类似 C++ 的 const 概念来防止这种情况。

关于引用和指针的更多信息可以在我的(非常受欢迎的)文章 Addressing pointers 中找到。

比其他答案(非常好)更简单。

does it work similarly as in C and the procedure gets a pointer to the variable and uses this pointer.

是的,这就是引擎盖下发生的事情。该过程实际上获得了一个指向变量的指针,一个地址。但是编译器知道这一点,使其变得透明。因此,在声明参数 "a" 的过程中,语句:

a := a div 2;

可以用两种不同的方式编译。如果 a 参数是正常声明的,即按值传递,则语句编译为:

1 - 加载地址 "a"
处的值 2 - 整数除以二
3 - 将结果存储在地址 "a"

如果参数声明为 var,意思是通过引用传递,编译器会:

1 - 加载地址 "a"
处的值 2 - 在刚刚加载的地址加载值(这是一个指针解引用)
3 - 除法
4 - 存储回来

如果源码为:

,以上4条语句正是C编译出来的
*a = *a / 2;

I guess I could formulate my question as: Does Pascal support true passing by reference ...?

答案是肯定的,通过引用传递是正确的,而且没有多少语言能如此干净利落地做到这一点。调用过程的源代码不会改变,无论该过程是用 "by reference" 还是 "by value" 调用的。同样,编译器知道如何传递形式参数,因此隐藏了细节,这与 C 不同。 例如,调用一个需要按值传递参数的过程:

myproc(a);  // pascal and C are identical

如果程序希望通过引用传递,情况就会有所不同:

myproc(a);  // pascal: identical to before

myproc(&a); // C: the language does not hide anything

关于最后一点,有人认为C 最好,因为它强制程序员知道传递的变量可以被过程(函数)修改。相反,我认为 pascal 更优雅,程序员应该知道程序将做什么。

所有这些都是针对 "simple" 类型的。如果我们谈论字符串(现代 Pascal 有两种风格),在 Pascal 中是一样的。编译器复制,增加引用计数,做任何需要的事情来完全支持按值或引用传递。 C语言没有类似的东西。

如果我们谈论 类,事情就不同了,但是由于 类.

的语义,它们 肯定 不同

我希望对另一个完整的答案有所补充。

术语“引用”是在编程语言的早期发明的,在 Algol、Fortran 或 Pascal 中。

引用是共享别名。 reference 的基本技术 属性 是共享实例的地址。在 C++ 中,引用等同于 de-referenced 指针。

// C++
int z = 29; // instance
int& alias = z; // reference
int* p = &alias; // pointer, address of z
int& r = *p; // reference

在 Pascal 中,引用仅作为过程或函数的 var-parameters 或 de-referenced 指针存在

// PASCAL
z: INTEGER = 29; // instance
p: ^INTEGER = @z; // pointer, address of z
// p^ is a reference


PROCEDURE RefDemo(var x: integer)
VAR q: ^INTEGER;
BEGIN
   q =@x;
   RefDemo(q^);
END;

var x: INTEGER 在 C++ 中存在等效项

void RefDemo(short int& x)
{
    short int* q = &x;
    RefDemo(*q);
}

回想起来,这样的引用 p^ 也存在于 C 中,如 *p。虽然发明者没有使用这个命名。他们甚至决定将指针称为值,而不是引用实例 (-value)。

现在对对象做同样的事情

// Pascal
type 
   Books = OBJECT
      title: packed array [1..50] of char;
      author: packed array [1..50] of char;
      subject: packed array [1..100] of char;
      book_id: integer;
   end;
...

PROCEDURE RefDemo(var book: Books)
VAR q: ^Books;
    otherBook: Books;
BEGIN
   book.author[1] := '-';
   book := otherBook; // assigns all attributes
   RefDemo(q^);
END;

// C++
class Books
{
public:
   char title[50];
   char author[50];
   char subject[50];
   short int book_id;
}
...

void RefDemo(Books& book)
   Books *q;
   Books otherBook;
{
   q = &book;
   book.author[0] = '-';
   book = otherBook; // assigns all attributes [1]      
   RefDemo(*q);
}

// C emulation
typedef struct _Books
{
   char title[50];
   char author[50];
   char subject[50];
   short int book_id;
} Books;
...

void RefDemo(Books* book)
   Books *q;
   Books otherBook;
{
   q = book;
   book->author[0] = '-';
   *book = otherBook; // assigns all attributes [1]       
   RefDemo(*q);
}

离题,但为了完成评论:

在 Java 中,对象参数的行为与 C 中的“By-Reference”模拟非常相似,只是我们没有 de-referentiation 运算符和 built-in 赋值运算符来完成[1].

中的对象实例

Java 的设计者遵循 C 的想法并打破了 [=] 的范式58=]是面向对象的语言,他们将对象引用视为值而不是对象实例,这就是无尽的起源关于 calling-conventions.

的讨论

我们永远不会知道,对于像“Java is pass by value?”这样的甚至语法薄弱的语句,Niklaus Wirth 会怎么说。