Почему компоновщик не может предотвратить C++ статическое фиаско порядка инициализации?

Править: Измененный пример ниже к тому, который на самом деле демонстрирует SIOF.

Я пытаюсь понять всю тонкость этой проблемы, потому что это, кажется, мне крупнейшая дыра на языке. Я считал, что это не может быть предотвращено компоновщиком, но почему это так? Это кажется тривиальным для предотвращения в простых случаях, как это:

// A.h
extern int x;

// A.cpp
#include <cstdlib>

int x = rand();

// B.cpp
#include "A.h"
#include <iostream>

int y = x;

int main()
{
    std::cout << y; // prints the random value (or garbage)?
}

Здесь, компоновщик должен смочь легко решить, что код инициализации для A.cpp должен произойти перед B.cpp в связанном исполняемом файле, потому что B.cpp зависит от символа, определенного в A.cpp (и компоновщик, очевидно, уже должен разрешить эту ссылку).

Итак, почему это не может быть обобщено ко всем единицам компиляции. Если компоновщик обнаруживает круговую зависимость, она не может только привести ссылку к сбою с ошибкой (или возможно предупреждение, так как это может быть намерение программиста, которое я предполагаю, чтобы определить глобальный символ в одной единице компиляции и инициализировать ее в другом)?

Стандарт налагает какие-либо требования к реализации для обеспечения надлежащего порядка инициализации в простых случаях? Что такое пример случая, где это не было бы возможно?

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

6
задан 22 February 2010 в 14:55
поделиться

7 ответов

Линкеры традиционно просто связывают - т.е. разрешают адреса. Вы, похоже, хотите, чтобы они делали семантический анализ кода. Но у них нет доступа к семантической информации - только к куче объектного кода. Современные компоновщики, по крайней мере, могут обрабатывать большие имена символов и отбрасывать дублирующиеся символы, чтобы сделать шаблоны более удобными для использования, но до тех пор, пока компоновщики и компиляторы независимы, это все. Конечно, если компоновщик и компилятор разрабатываются одной командой, и если эта команда - большая корпорация, в компоновщик может быть вложено больше интеллекта, но трудно понять, как стандарт для переносимого языка может требовать такого.

Если вы хотите узнать больше о компоновщиках, загляните на http://www.iecc.com/linker/ - это единственная книга о часто игнорируемом инструменте.

6
ответ дан 9 December 2019 в 20:42
поделиться

В вашем простом примере достаточно умный компоновщик действительно мог бы выяснить, что инициализация в A.o должна выполняться перед инициализацией в B.o, потому что B.o ссылается на символы, определенные в A.o.

Но такие простые примеры, как ваш, на самом деле не демонстрируют большой проблемы, и уж точно не являются чем-то уровня "фиаско". Вот несколько более сложный пример.

// externs.h
extern int a;
extern int b;

// A.cpp
#include "externs.h"

int a = 5;
int aa = b;

// B.cpp
#include "externs.h"
int b = 10;
int bb = a;

Стандарт требует, чтобы переменные в одной единице компиляции инициализировались в порядке объявления, поэтому a должен быть инициализирован перед aa, а b должен быть инициализирован перед bb, но больше никаких требований к порядку нет. Инициализации из единицы компиляции разрешается чередовать с инициализациями из других единиц компиляции.

Существует по крайней мере один порядок инициализации, который обеспечит инициализацию всех переменных до того, как они будут использованы для инициализации чего-либо еще, и при этом будет подчиняться стандарту:

  1. a
  2. b
  3. bb
  4. aa

Компоновщик имеет лишь ограниченную информацию об этой программе. Он знает, что скомпилированный файл A.o определяет два символа, a и aa, и что он ссылается на внешний символ b. Аналогично, он знает, что B.o определяет b и bb и ссылается на внешний символ a. Эти два объектных файла являются взаимозависимыми, поэтому компоновщик не может использовать ту же технику, которую он мог бы использовать в вашем примере. В этом примере ему нужно знать, что для инициализации B.o должен быть определен только a. Однако информация, записанная в объектных файлах, не настолько конкретна. Она не содержит зависимостей между символами.

1
ответ дан 9 December 2019 в 20:42
поделиться

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

Один из способов разрешения такой конструкции состоит в компиляции кода инициализации для каждого модуля, который выполняется до основного (), как это делает # pragma startup в некоторых реализациях.

Но на самом деле, как часто модуль объявления не знает, каковы значения инициализации?

-121--4244537-

Ну, чтобы получить представление о возможностях HTML5/canvas + JavaScript, вы можете взглянуть на этот , который является реализацией Wolfenstein 3D чисто в HTML5, без использования какого-либо Flash (обратите внимание, что вам нужен браузер с поддержкой холста, такой как Firefox, чтобы увидеть эту работу).

-121--1836515-

Традиционные линкеры не смотрят на исходный код или даже на AST, а существующие форматы файлов объектов предоставляют достаточно минимальную информацию об экспортированных и внешних символах.

0
ответ дан 9 December 2019 в 20:42
поделиться

Просто используйте ClassLoader, чтобы получить ресурсы от плагина MyCodeGenerator Maven.

Добавьте что-то подобное в ваш MyCodeGenerateMojo

    URL getTemplate(String fileName) {
        return this.getClass().getResource(fileName);
    }

В плагине MyCodeGenerator Maven добавьте шаблоны в каталог src/main/resources (не забудьте использовать правильную запись пакета (каталоги) в этом каталоге).

-121--4859379-

Я использовал LingPipe - номера люкс библиотек Java для лингвистического анализа человеческого языка - для задач интеллектуального анализа текста (и других связанных с ними).

Это очень хорошо документированный пакет программного обеспечения, и сайт содержит несколько учебных пособий, в которых подробно объясняется, как выполнить определенную задачу с помощью LingPipe, например, распознавание именованных объектов . Существует также группа новостей, в которой можно разместить любой вопрос о программном обеспечении (или задачах, связанных с NLP) и получить быстрый ответ от самих авторов пакета; и, конечно, блог .

Исходный код также очень прост в следовании и хорошо документирован, что для меня всегда является большим плюсом.

Что касается алгоритмов машинного обучения, то их достаточно, от Naive Bayes до Conditional Random Поля . С другой стороны, для алгоритмов согласования словарей они имеют ExactDicitonireChunker , который является реализацией алгоритма Ахо-Корасича (очень, очень быстрый алгоритм для этой задачи).

В целом, я думаю, что это один из лучших пакетов программного обеспечения NLP для Java (я не использовал каждый пакет, который там, поэтому я не могу сказать, что он лучший), и я определенно рекомендую его для задачи, которая у вас под рукой.

-121--2827322-

Хотя компоновщик, возможно, мог бы это сделать, большинство примеров того, где вам это нужно, также являются примерами плохого кода, лишенного когезии и имеющего высокую связь (обычно через ужас глобальных переменных). Твой пример такой пример.

Так что вряд ли это «фиаско»; это, вероятно, слишком сильное описание. Это лишь незначительное ограничение способа кодирования.

0
ответ дан 9 December 2019 в 20:42
поделиться

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

Изменить: с точки зрения стандарта решение этой проблемы совершенно тривиально: одно предложение требует, чтобы все объекты со статическим хранилищем длительность инициализируется до начала выполнения main () .К сожалению, почти все, что можно было бы сделать, - это поднять еще одну область, в которой практически никто не соответствует стандарту или (что еще хуже) даже не планирует это сделать. Чтобы это что-то значило, исполнители в комитете должны согласиться с тем, что это достаточно важно, чтобы они собирались его реализовать.

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

Это возвращает нас к тому, что я сказал изначально: даже если это может показаться серьезной проблемой для нас, как пользователей, очевидно не выглядит так для большинства разработчиков. Я вижу ряд причин, по которым это может быть. Во-первых, конечно, то, что C ++ не является ключевым пунктом в чьей-либо корпоративной повестке дня. Microsoft продвигает .NET. Sun / Oracle и IBM продвигают Java. У других есть свои планы, но ни один из них не пытается заставить вас использовать C ++. Мне кажется, что большинство из них считают это необходимым злом, а не чем-то, чему они действительно хотят посвятить какие-либо усилия. В таком случае работа над полным изменением структуры их компоновщика для решения этой конкретной проблемы, вероятно, даже будет открыта для рассмотрения, только если они получат много жалоб по этому поводу. Это как две проблемы.Во-первых, C ++ начинается как довольно небольшое сообщество, поэтому потребуется огромный процент их, прежде чем разработчики действительно заметят что-либо, что они говорят. Во-вторых, лишь небольшой процент программистов на C ++ все равно сталкивается с проблемами. Единственная причина, по которой они беспокоились или волновались, была бы в том, если бы это стало проблемой для их собственного внутреннего развития. К сожалению, у большинства нет причин беспокоиться о переносимости.

3
ответ дан 9 December 2019 в 20:42
поделиться

Это потому, что статическая инициализация - это совсем другое дело, чем инициализация среды выполнения. Инициализация x по своей природе в вашем примере является динамической. Но написано это как статическая инициализация. В основном это происходит из-за совместимости с десятилетиями практики C .

Одним из способов разрешения такой конструкции является компиляция кода инициализации для каждого модуля, который запускается перед main (), как #pragma startup в некоторых реализациях.

Но на самом деле, как часто модуль объявления не знает, каковы значения инициализации?

2
ответ дан 9 December 2019 в 20:42
поделиться

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

Комитеты по языковым стандартам поэтому попытаются ограничить требования, которые они предъявляют к реализации, особенно к более распространенным системам. В современных системах очень часто используются разные компиляторы, но есть общий компоновщик, и поэтому комитет будет чувствовать себя гораздо свободнее предъявлять требования к разработчикам компилятора, но упростить работу с компоновщиками.

Перегрузка функций C ++ зависела от поиска уловки, заставляющей ее работать с компоновщиками («изменение имени»). В стандарте C90 сказано, что имена переменных с внешней связью должны быть уникальными в первых шести символах без учета различных случаев. Обоснование (для версии ANSI 1989 года, это был IIRC, отказалось от стандарта ISO 1990 года) гласило, что комитет был очень недоволен сохранением этого ограничения, но чувствовал, что отказ от него сделает слишком трудным внедрение стандарта C на слишком многих системы с примитивными линкерами.

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

0
ответ дан 9 December 2019 в 20:42
поделиться
Другие вопросы по тегам:

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