编译器构造/已知语言概念实现
由于本书是关于编译器的,以下研究没有详细说明语法或语言设计,而是实现技术。如果您熟悉以下语言,了解这些熟悉的概念(如动态绑定)的实现方式可能有助于理解编译器。
此外,我们没有考虑对语言设计决策的实际影响。一些棘手的情况,比如引用依赖的问题或运算符的优先级,在大多数情况下可能不会发生,但这些通常是语言实现者的关注点。
在 Java 中,有四种方法调用,分别是 invokestatic、invokespecial、invokevirtual、invokeinterface。顾名思义,第一个用于调用静态方法,其余用于调用实例方法。由于静态方法不能被覆盖,因此 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 是超类方法的名称。
在语义上,invokeinterface
与 invokevirtual
不同,但它可以给编译器关于调用的提示。
类方法可以用 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 中,每个类都是 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—,它无法生成发送消息的代码,因为返回浮点数通常与返回整数不同。