Я обращаюсь только эта часть вопроса:
Кроме того, я был бы очень признателен, если кто-нибудь может привести пример как реально выглядит код сборки MIPS
Я переписал ваш пример на C++ таким образом, что компилятор C++ с несколькими коммутаторами не будет оптимизировать полностью исключить его и сохранить поля и вызовы (если вы задаетесь вопросом, почему используется volatile
, а также некоторые другие вещи, просто чтобы предотвратить компилятор для сборки сборки, например return value = 5, return
... Компиляторы на C++, как правило, немного раздражают друг друга с оптимизацией, когда вы хотите просто увидеть некоторый «примерный» код).
class A {
public:
volatile int x;
virtual void f(){
++x;
}
};
class B : public A {
public:
volatile int y;
B(int i) {
y = i;
x = i-1;
}
virtual void f(){
x += 2;
}
void g() {
f();
x += 3;
++y;
}
};
int footest(int in) {
B* obj = new B(in);
A* obj_A_alias = obj;
obj_A_alias->f(); // calling B::f(), because f is virtual
obj->g();
obj->f();
obj->A::f(); // forcing A::f() call (on B instance)
int result = obj->x + obj->y;
delete obj;
return result;
}
Теперь, если вы поставите это в http://godbolt.org/ и настроить компилятор MIPS GCC 5.4 с вариантами -O3 -std=c++11 -fno-loop-optimize -fno-inline
, вы получите этот результат:
$LFB0 = .
A::f():
$LVL0 = .
lw $2,4($4)
addiu $2,$2,1
sw $2,4($4)
j $31
nop
$LFB7 = .
B::f():
$LVL1 = .
lw $2,4($4)
addiu $2,$2,2
sw $2,4($4)
j $31
nop
$LFB3 = .
A::A():
$LVL2 = .
$LBB2 = .
lui $2,%hi(vtable for A+8)
addiu $2,$2,%lo(vtable for A+8)
j $31
sw $2,0($4)
$LBE2 = .
A::A() = A::A()
$LFB5 = .
B::B(int):
$LVL3 = .
addiu $sp,$sp,-40
sw $17,32($sp)
move $17,$5
sw $31,36($sp)
sw $16,28($sp)
$LBB3 = .
jal A::A()
move $16,$4
$LVL4 = .
addiu $2,$17,-1
$LBE3 = .
lw $31,36($sp)
$LBB4 = .
sw $17,8($16)
sw $2,4($16)
lui $2,%hi(vtable for B+8)
$LBE4 = .
lw $17,32($sp)
$LVL5 = .
$LBB5 = .
addiu $2,$2,%lo(vtable for B+8)
sw $2,0($16)
$LBE5 = .
lw $16,28($sp)
$LVL6 = .
j $31
addiu $sp,$sp,40
B::B(int) = B::B(int)
$LFB8 = .
B::g():
$LVL7 = .
lw $2,0($4)
addiu $sp,$sp,-32
sw $16,24($sp)
sw $31,28($sp)
lw $25,0($2)
jalr $25
move $16,$4
$LVL8 = .
lw $2,4($16)
lw $31,28($sp)
addiu $2,$2,3
sw $2,4($16)
lw $2,8($16)
addiu $2,$2,1
sw $2,8($16)
lw $16,24($sp)
$LVL9 = .
j $31
addiu $sp,$sp,32
$LFB9 = .
footest(int):
$LVL10 = .
lui $28,%hi(__gnu_local_gp)
addiu $sp,$sp,-32
addiu $28,$28,%lo(__gnu_local_gp)
sw $16,24($sp)
move $16,$4
$LVL11 = .
sw $31,28($sp)
lw $25,%call16(operator new(unsigned int))($28)
1: jalr $25
li $4,12 # 0xc
$LVL12 = .
move $5,$16
move $16,$2
$LVL13 = .
jal B::B(int)
move $4,$2
$LVL14 = .
$LVL15 = .
jal B::f()
move $4,$16
$LVL16 = .
jal B::g()
move $4,$16
$LVL17 = .
lw $2,0($16)
lw $25,0($2)
jalr $25
move $4,$16
$LVL18 = .
jal A::f()
move $4,$16
$LVL19 = .
move $4,$16
lw $28,16($sp)
lw $2,4($16)
lw $16,8($16)
$LVL20 = .
lw $25,%call16(operator delete(void*))($28)
$LVL21 = .
1: jalr $25
addu $16,$2,$16
$LVL22 = .
move $2,$16
lw $31,28($sp)
lw $16,24($sp)
$LVL23 = .
j $31
addiu $sp,$sp,32
typeinfo name for A:
.ascii "1A\000"
typeinfo for A:
.word vtable for __cxxabiv1::__class_type_info+8
.word typeinfo name for A
typeinfo name for B:
.ascii "1B\000"
typeinfo for B:
.word vtable for __cxxabiv1::__si_class_type_info+8
.word typeinfo name for B
.word typeinfo for A
vtable for A:
.word 0
.word typeinfo for A
.word A::f()
vtable for B:
.word 0
.word typeinfo for B
.word B::f()
Try it на фактическом месте, так что вы получите также цветные подсказки, какая часть кода принадлежит той части источника (а также компиляторы MIPS64, если это ваша целевая платформа).
EDIT: Кроме того, вы должны, вероятно, попробовать -O0
вариант также, что выход будет очень вероятно, гораздо больше связаны с тем, что вы можете разумно достигнуть с одним студентом только проект.
Я не достаточно хорошо в MIPS, чтобы объяснить вам, что происходит там, и у меня есть время для этого, но если вы производите компилятор, вы должны понимать это лучше, чем у меня.
C++ источник демонстрирует, как виртуальный вызов делается ($LVL17
), не виртуальный родительский вызов ($LVL18
), не виртуальный самостоятельный вызов ($LVL16
), и доступ к значениям полей ($LVL19
).
Теперь имейте в виду, что это профессионально оптимизированный инструмент, поэтому, если вы закончите с менее оптимальным решением, все должно быть хорошо. Также имейте в виду, что компиляция Java и C++ немного отличается, в Java конечный результат не является «статичным», как C++, поэтому, возможно, у вас недостаточно информации для оптимизации так агрессивно, как C++, вызовы виртуальных функций просто жестко запрограммированы с целевым адресом или полями тоже ...
В конце концов, если это Java, вы не можете ожидать, что он будет оптимальным, поскольку для управляемого языка исполнения он является прекрасным и вместе с высококачественный JIT-компилятор, базовая скорость кода может быть на одном уровне с C++, но как только вы попадаете в неэффективные структуры данных Java, C++ «swoooshs» вдоль и за горизонтом.
Поля не могут быть виртуальными, поэтому нет необходимости в таблице. Если вы хотите узнать, как выглядит asm, используйте существующий компилятор. – Jester
Обратите внимание, что VMT не требуется * один раз для каждого экземпляра * - * Один раз для каждого класса *, и всех экземпляров, содержащих указатель, достаточно. – tofro