跳转到内容

编译器构造/已知语言概念实现

来自维基教科书,开放世界开放图书

已知语言概念实现

[编辑 | 编辑源代码]

由于本书是关于编译器的,以下研究没有详细说明语法或语言设计,而是实现技术。如果您熟悉以下语言,了解这些熟悉的概念(如动态绑定)的实现方式可能有助于理解编译器。

此外,我们没有考虑对语言设计决策的实际影响。一些棘手的情况,比如引用依赖的问题或运算符的优先级,在大多数情况下可能不会发生,但这些通常是语言实现者的关注点。

在 Java 中,有四种方法调用,分别是 invokestaticinvokespecialinvokevirtualinvokeinterface。顾名思义,第一个用于调用静态方法,其余用于调用实例方法。由于静态方法不能被覆盖,因此 invokestatic 非常简单;它本质上与在 C 中调用函数相同。

我们现在来看看实例方法调用的机制。考虑以下代码片段。

class A {
  public static int f () { return 1; }
  private int g_private () { return 2; }
  public final int g_final () { return 3; }
  public int g_non_final () { return 4; }

  public void test (A a) {
    a.f ();  // static; this is always 1.
    a.g_private ();  // special; this is always 2.
    a.g_final (); // special; this is always 3.
    a.g_non_final (); // virtual; this may be 4 or something else.
  }
}

class B extends A {
  public int g_non_final () { return 6; }
}

class C extends B {
  public int g_non_final () { return 7; }
  public int foo () { return A.this.g_non_final (); }
}

invokestatic 使用对类名和方法名的引用被调用,并从堆栈中弹出参数。表达式 A.f (2) 被编译为

iconst_2           // push a constant 2 onto the stack 
invokestatic A.f   // invoke a static method
// the return value is at the top of the stack.

在 Java 中,私有方法不能被覆盖。因此,必须根据类来调用方法,而不管对象是如何创建的。invokespecial 允许这样做;该指令与 invokestatic 相同,除了它除了提供的参数外,还弹出对象引用。到目前为止,动态绑定没有使用,并且在运行时不需要有关私有方法的绑定信息。

具体来说,invokespecial 可以用于 (1) 调用私有方法或 (2) 调用超类的某个方法(包括超类的构造函数,即 <init>)。要调用除 <init> 之外的超类方法,必须像 super.f () 一样编写,其中 f 是超类方法的名称。

在语义上,invokeinterfaceinvokevirtual 不同,但它可以给编译器关于调用的提示。

类方法

[编辑 | 编辑源代码]

类方法可以用 static 限定符定义。私有类方法可以在同一个对象中,如果它们属于不同的类。两个公共类方法不能在同一个对象中;换句话说,类方法不能被覆盖。这也意味着 final 限定符对类方法在语义上是无意义的。

每个字段都是根据类访问的。考虑以下内容。

class A {
  public int i = 2;
}

class B extends A {
  public int i = 3;
}
B b = new B ();
A a = b;
b.i++;  // this would be 3 + 1 = 4
a.i++;  // this would be 2 + 1 = 3

换句话说,访问控制修饰符(无、公共、私有和受保护)只影响类的客户端是否可以访问给定字段。这意味着 Java 虚拟机可能会忽略访问标志,以相同的方式处理每个字段。

Objective-C

[编辑 | 编辑源代码]

对象和字段

[编辑 | 编辑源代码]

在 Objective-C 中,每个类都是 C 中的一个结构体。也就是说,

@interface A
{
  int a, b, c
}

@end

将被实现为

struct A {
  int a, b, c;
  .... // some runtime information
};

因此,由于 Objective-C 中的每个对象都是对堆中内存块的指针。因此,访问字段的方式与访问结构体成员的方式相同。也就是说,

id obj = [A alloc];

这种方案的含义是,虽然对象自然适合非 OOP C 程序,但一个缺点是字段不能被“隐藏”。也就是说,

@interface A
{
  @private
  int a;
}
@end

@interface B : A
{
  @private
  int a;
}
@end

这会导致重复成员错误。这与 Java 中的情况形成对比。

最后,由于方法的选择是在运行时发生的(与 Java 或 C++ 中的情况相反),因此方法的处理方式与字段不同。

在 Objective-C 中,方法的选择是在运行时发生的。编译器可能会发出关于可能类型错误名称的警告,因为编译器可以知道程序中定义的一组选择器名称。但是,这在语义上不是必需的;任何消息都可以发送到任何对象。

在语义上,消息的发送者会检查给定对象是否响应消息,如果没有,则尝试其超类,如果没有,则尝试其超类,依此类推。

可能会出现一个复杂情况,例如,当有两个选择器具有不同的返回值类型时。考虑以下情况。

@interface A { }
- (float) func: (int) i
@end

@interface B { }
- (int) func: (int) i
@end

在这种情况下,因为编译器无法知道对象将响应哪个方法--(float) func 或 (int) func—,它无法生成发送消息的代码,因为返回浮点数通常与返回整数不同。

华夏公益教科书