169 похожих чатов

Народ, я тут пытаюсь понять как компилятор должен вычислить оффсет

для полей литеральных объектов (структурная типизация) - когда мы в функции объявляем что принимаем объект с таким то полем
const someFunc = (obj: { a: number }) => {
obj.a += 1;
};
но передавать мы можем объекты где помимо этого поля могут быть разнообразные сочетания других полей
someFunc({a: 1, b: 1})
someFunc({x: 1, a: 1, z: 1})
И я вспомнил что эта проблема похожа на проблему множественного наследования и появился вопрос - кто-нибудь знает как с++ компиляторы понимают по какому оффсету расположено поле с таким-то именем в случае множественного наследования?
Представим себе 26 базовых классов каждый из который объявляет одно поле
class A {
int a;
}
class B {
int b;
}
...//еще 22 класса
class Y {
int y;
}
class Z {
int z
}
И есть функция которая объявляет что в качестве параметра принимает объекты с полем "k"
void someFunc(K& obj){
obj.k += 1;
}
То есть к этой функции можно передавать объекты классов которые наследуются от класса K
Но дальше с множественным наследованием мы можем создать и передать функции объекты с различными сочетаниями других полей, например

class KAB: K, A, B {} //{k: 0, a: 0, b: 0}
class KAO: K, A, T {} //{k: 0, a: 0, t: 0}
class KNJ: K, A, T {} //{k: 0, n: 0, j: 0}
//много других сочетаний
someFunc(new KAB())
someFunc(new KAO())
someFunc(new KNJ())
Тут можно заметить что поле "к" находится по первому оффсету, а если нет то мы отсортируем в общем сделаем так чтобы оффсеты полей которые принимает функция будут находиться в начале объекта. Ок, это будет работать в случае если у нас есть только одна такая функция, но что если функций много и каждой нужно знать оффсет поля со своим именем или даже несколько таких полей. Тут уже не получится сделать так чтобы все поля с таким именем лежали по одинаковым оффсетам потому что сочетания полей (базовых класов) могут быть самые разные
void someFunc(K& obj){
obj.k += 1;
}
void someFunc2(T& obj){
obj.k += 1;
}
auto obj = new KOT();
someFunc(obj)
someFunc2(obj)

auto obj = new AKT();
someFunc(obj)
someFunc2(obj)
Поверхностное гугление не дало ответов на вопросы как компилятор с++ вычисляет оффсеты для таких различных сочетаний базовых классов. Есть пейпер от Страуструпа https://www.usenix.org/legacy/publications/compsystems/1989/fall_stroustrup.pdf но там ничего не написано про такие сочетания, есть общие фразы вроде ": the compiler knows the location in the object of each
member and generates the appropriate code" или для вызова методов которые обращаются к this "This constant, detta(B), is the relative position of the B part of c. This delta is
known to the compiler that transforms the call", но каким образом компилятор знает оффсеты если сочетания базовых классов могут быть самыми разнообразными непонятно. Если отсортировать поля то все равно будут разные оффсеты из-за того что не все поля присутствуют в объекте. Единственный вариант сделать чтобы поля имели одинаковые оффсеты это сделать убер-объект большого размера который будет включать каждое поле даже если оно не нужно но смысла в этом нет так как это будут бессмысленные затраты по памяти.
В общем я тут придумал схему как можно вычислить оффсеты (храним в хедере объекта битмап наличия полей и вычисляем оффсет через количество предыдущих установленных бит popcnt инструкцией) но хотелось бы узнать как с++ компиляторы решают эту проблему (может я пропустил что-то очевидное)

4 ответов

16 просмотров

Во-первых, структурное подтипирование не имеет ничего общего с множественным наследованием. Во-вторых, как обычно реализуют множественное наследование компиляторы C++ написано в Design and Evolution. TL;DR: компилятор просто кладёт рядом данные и vtable, соответствующие каждому из предков, поскольку функция принимает указатель и ожидает там увидеть данные и vtable по одним и тем же смещениям для каждого конкретного класса — просто передаём ей указатель на нужный кусок (заголовка) объекта. В-третьих для структурной типизации и подтипирования нужно делать "честный" доступ по имени (хеш-таблицу как в JS/Python/whatever). Вероятно, в случае статической структурной типизации (особенно с immutable data) можно вставлять неявные функции преобразования между форматами, в редких случаях когда они не совпали после сортировки полей.

Богдан- Автор вопроса
Alexander Chichigin
Во-первых, структурное подтипирование не имеет нич...

А почему это структурное подтипирование не имеет ничего общего с множественным наследованием? Я вижу полную эквивалентность. Представим что при написании программы на js мы передаем от функции к функции объекты-литералы самых разных шейпов {a, b} {a, b, c} {b, c, d} {a, x, y} {b, someField} {someField, someField2} Все эти литералы можно представить как класс который наследует от некоторого количества базовых классов (н-классов по одному для каждого уникального поля) class Base1 { a } class Base2 { b } class Base3 { c } class Base4 { d } class Base5 { x } class Base6 { y } class Base7 { someField } class Base8 { someField2 } и тогда любой литеральный объект можно представить как наследования нескольких базовых классов const obj = {a, x, y} -> new class extends Base1, Base5, Base6 const obj2 = {b, someField} -> new class extends Base2, Base7 и тогда задача вычисления оффсета внутри функции для объекта который имеет такой-то интерфейс (имеет поле c таким то именем) function someFunction(obj: {someField}) { obj.someField += 1; } someFunction({b, someField}) someFunction({someField, someField2, a}) someFunction({a, someField2, b, c}) сводится к проблеме вычисления оффсета при множественном наследовании function someFunction(obj: Base7) { obj.someField += 1; } someFunction(new class extends Base2, Base7) someFunction(new class extends Base7, Base, Base1) someFunction(new class extends Base1, Base7, Base2, Base3)

Вы же сами только что написали. Какая проблема, что смещения разные, если компилятор их все знает, потому что сам их и высчитал? Передвигает указатель на нужное смещение и передаёт.

Похожие вопросы

Обсуждают сегодня

@MrMiscipitlick А можешь макрос написать, который будет вычислять смещение относительно переданных меток? Просто .label1-.label2, и вернуть значение.
КТ315
35
я не магистр хаскеля, но разве не может лейзи тип конвертнуться в не-лейзи запросив вычисление содержимого прям при инициализации?
deadgnom32 λ madao
100
А еще в перле можно уже @arr1 + @arr2?
Sergei Zhmylove
53
Подобного рода ;Следующие три строки это директивы ассемблера, ;которые можно не задавать, т.к.работаем в Visual Studio. ;Символ ";" - это начало однострочного комментария ...
Егор Анелькин
3
Кто-нибудь знает почему SPM клонирует репо целиком? Некоторые репы просто огромные, как та же swift-syntax которая нужна для использования макросов. Сначала подумал, что это...
iMike
6
Привет всем. появился вопрос. Разрабатываю сайт, в данный момент он запущен. Хостинг beget. Добавляю на сайт яндекс метрику с помощью полей client-settings (взято отсюда http...
Andrew
2
Подскажите, где смотреть результат выполнения программы? Код: ;.686 ;Система команд процессора 686 ;.MODEL FLAT,stdcall ;Модель памяти плоская, станда...
Егор Анелькин
5
еще вопрос, допустим мы создадим char массив из 10 элементов и присвоим ему через сканф 10 символов. и выведем все символы. Хотел спросить последний элемент /0 будет включать...
Anthem
11
открыть папку в проводнике: 1 - ShellExecute 2 - ExecuteProcess 3 - OpenDocument что лучше выбрать?
Alexey Kulakov
12
;.686 ;Система команд процессора 686 ;.MODEL FLAT,stdcall ;Модель памяти плоская, стандартный ;вызов процедуры ;option casemap:no...
Егор Анелькин
1
Карта сайта