Элегантное решение загадки круговой зависимости

Поэтому я разрабатываю язык программирования, который компилируется в байт-код для выполнения виртуальной машины, а также в C в качестве промежуточного языка для компиляции в собственный двоичный файл. Я выбрал C, потому что он достаточно низкоуровневый и переносимый, что позволяет сэкономить огромное количество усилий за счет повторного использования существующих компиляторов и отсутствия необходимости писать компиляторы для сборки для каждой отдельной платформы и ее странностей.

Но существующие компиляторы имеют свои недостатки, одним из которых является проблема циклической зависимости. Я хочу решить циклические зависимости элегантным способом (в отличие от C / C ++) без неудобных предварительных объявлений, без необходимости использовать указатели, дополнительную косвенность и бесполезную трату памяти, без необходимости отделять объявления от определений и так далее ... Другими словами, устраните эту проблему от разработчика, как это делают некоторые языки программирования.

На мой взгляд, основная проблема нынешних компиляторов C / C ++ заключается в том, что они не могут «заглядывать в будущее», хотя на самом деле это не будущее, поскольку намерение программиста уже выражено в коде, у моего компилятора такой проблемы нет. , он не знает ничего, кроме определенной точки прогресса синтаксического анализа, он знает размеры объектов с круговыми зависимостями и может вычислять соответствующие смещения и тому подобное.

Я уже реализовал «поддельное» наследование, которое просто выполняет «встроенное расширение» членов унаследованных структур, поэтому я думаю, что могу использовать тот же подход и для фактического поддельного агрегирования. В самом простом и простом примере:

typedef struct {
    int a;
} A;

typedef struct {
    A a;
    int b;
} B;

становится:

typedef struct {
    int A_a;
    int b;
} B;

и компилятор немного «переводит»:

B b;
b.a.a = 7;

становится:

b.A_a = 7;

Таким же образом все структуры сворачиваются в единую корневую структуру, которая содержит только примитивные типы. Таким образом, в структурах, размеры которых заранее не известны, не используются абсолютно никакие типы, поэтому порядок определения становится неактуальным. Естественно, этот беспорядок скрыт от пользователя и предназначен только для «глаз» компилятора, в то время как пользовательская сторона остается структурированной и читаемой. И само собой разумеется, но двоичный след сохраняется для совместимости с обычным кодом C / C ++, свернутая структура двоично идентична обычной структуре, которая будет использовать агрегацию или наследование.

Итак, мой вопрос: звучит ли это как хорошая идея? Что-нибудь, что может пойти не так, мне не хватает?

РЕДАКТИРОВАТЬ: Я стремлюсь решить проблемы, связанные с C / C ++, с круговыми зависимостями, а не логический парадокс «курица или яйцо». Очевидно, что два объекта не могут содержать друг друга, не создавая бесконечного цикла.


person Community    schedule 04.11.2013    source источник
comment
Впечатляет, но есть ли реальный смысл во встроенном расширении? Отличается ли сгенерированный код при доступе к b.A_a, а не к b.a.a? Я ожидал, что это будет точно так же, поэтому вы делаете много работы, чтобы что-то оптимизировать без особой выгоды. Просто спрашиваю.   -  person unwind    schedule 04.11.2013
comment
@unwind - преимущество в том, что циклическая зависимость становится исчезнувшей и неуместной проблемой.   -  person    schedule 04.11.2013
comment
Ах я вижу. Возможно, было бы яснее, если бы в вашем C-подобном примере A и B были в обратном порядке. Как показано, объявления C хороши, и нет проблем с цикличностью.   -  person unwind    schedule 04.11.2013
comment
@unwind - код демонстрирует концепцию решения проблемы, а не саму проблему, которая объясняется только в тексте. Я предполагал, что люди прочитают текст вопроса, а не только исходный код;)   -  person    schedule 04.11.2013
comment
Пожалуйста, извините, если я ошибаюсь, но я не решил, что вы решили круговые зависимости, т.е. A определяется в терминах B, а B одновременно определяется в терминах A. Я предполагаю, что вместо этого вы пытаетесь выяснить правильный порядок объявления для компилятора C.   -  person Jonas Bötel    schedule 04.11.2013
comment
@LumpN - да, вы правы, проблема агрегирования объектов с круговой зависимостью остается, поскольку это логический парадокс (например, курица и яйцо), но он значительно упрощает доступ объектов к функциям друг друга, упорядочивая объявление и определение не имеют значения, поскольку каждая структура сводится к примитивным типам. Я только стремлюсь решать круговые зависимости так, как это решает JAVA, и даже в JAVA вы все равно получаете переполнение стека, если два объекта с круговой зависимостью в конечном итоге бесконечно вызывают конструкторы друг друга.   -  person    schedule 04.11.2013
comment
@ user2341104 Я понимаю идею и ваш подход. Вы говорите о зависимостях типов, которые могут быть объявлены не в требуемом для C. порядке. По-прежнему здесь нет круговоротов, просто упорядочение несоответствия / опережения. Мой вопрос в том, почему ваш компилятор не сохраняет все структуры как есть, а вместо этого просто генерирует для них код C в требуемом порядке?   -  person Jonas Bötel    schedule 04.11.2013
comment
Но если вы намереваетесь использовать A_* члены B, как если бы это был A (т. Е. Преобразование его в A*), у вас будут бесконечные проблемы с выравниванием, заполнением и т.п.   -  person rodrigo    schedule 04.11.2013
comment
@ user2341104 Еще одна вещь - циклические ссылки на файлы заголовков. Их легко решить с помощью #ifdefs, как в C ++.   -  person Jonas Bötel    schedule 04.11.2013
comment
@LumpN - проще было бы свернуть все структуры до примитивных типов, а для отслеживания порядка потребуется дополнительная реализация. Но я так и не решился, поэтому задаю вопрос. Я мог бы в конечном итоге отслеживать порядок зависимостей. В общем, я бы предпочел встроенные реализации, такие как JAVA, вместо разделения на заголовки и источники ради повышения производительности.   -  person    schedule 04.11.2013
comment
@ user2341104 вам также необходимо выяснить правильный порядок зависимостей для встраивания. Вероятно, он уже там, скрыт в какой-то вашей рекурсивной функции встраивания.   -  person Jonas Bötel    schedule 04.11.2013
comment
@ user2341104: Но вам не нужно разделять заголовок / источник. Вы можете генерировать только исходные файлы и копировать определение структуры в каждый файл, который вам нужен. Пока вы соблюдаете правило единого определения, все будет в порядке.   -  person rodrigo    schedule 04.11.2013


Ответы (1)


Вы не можете безопасно использовать указатели на подструктуры, потому что вы не можете получить указатели на «совместимые типы», указывая на примитивные члены. Например. после

struct Foo {
    short a;
    int b;
};

struct Bar {
    struct Foo foo;
};

struct Bar bar;

указатели &bar.foo и &bar.foo.a имеют разные типы и не могут использоваться взаимозаменяемо. Они также не могут быть преобразованы в типы друг друга без нарушения строгого правила псевдонима, вызывающего неопределенное поведение.

Этой проблемы можно избежать, каждый раз вставляя все определение struct:

struct Bar {
    struct { short a; int b; } foo;
};

Теперь &bar.a - это указатель на struct {short; int;}, который является совместимым типом для struct Foo.

(Также могут быть различия в заполнении / выравнивании между членами с типом struct и примитивными членами, но я не смог найти их примера.

person Fred Foo    schedule 04.11.2013
comment
Я сохраняю двоичную совместимость, вставляя байты заполнения вручную, а не вручную, но мой компилятор делает это, чтобы соответствовать тому, что компилятор C будет делать при агрегировании структур. - person ; 04.11.2013