|
Программирование >> Полиморфизм без виртуальных функций в с++
void g() { f(l); связывается с f Fi f(l, asdf ); связывается с f FiPc f(l,l); попытка связать с f Fdd ошибка во время компоновки: f Fdd не определена Это оставляет открытым вопрос о том, как вызвать С- или С++-функции. Для этого программист должен указать, что функция имеет С-компоновку. В противном случае предполагается, что это C-+-фyнкция, и ее имя кодируется. С этой целью в С++ была введена спецификация компоновки: extern С ( double sqrt(double); sqrt(double) имеет С-компоновку Спецификация компоновки не затрагивает семантику программы, в которой используется sqrt (), а лишь дает ко.\шилятору инфор.мацию, что при генерировании объектного кода он должен использовать для sqrt () принятые в С соглашения об именах. Это означает, что имя данного экземпляра sqrt () будет равно sqrt или sqrt, или иному варианту, в зависимости от конкретной системы. Можно представить себе систему, в которой для С приняты правила типобезопасной компоновки из С++, так что видимое компоновщику имя функции sqrt () будет sqrt Fd. Естественно, кодирование типа с помощью добавления суффикса - лишь один из возможных способов реализации, но он был успешно применен в Cfront, а затем широко растиражирован. У этого способа есть важные свойства; простота и совместимость с существующими компоновщика.ми. Такая реализация идеи типобезопасной компоновки не является абсолютно безопасной, но ведь в любом случае лишь очень немногие из полезных систем безопасны на 100%. Более полное описание схе.мы кодирования и.мен, примененной в Cfront, приводится в [ARM, §7.2с]. 11.3.3. Анализ пройденного пути Видимо, мы правильно определили приоритеты: типобезопасная компоновка, на/тчие разумной реализации и возможность явной компоновки с програ,ммами, написанны.ми на других языках. Как и ожидалось, с по.мощью новой систе.мы компоновки решался целый ряд проблем. Помимо всего прочего, в процессе перехода к новому стилю обнаружилось на удивление большое число ошибок при компоновке старых программ, написанных на С и С++. Я тогда отметил: При переходе на типобезопасную ко.мпоновку чувствуеп]ь себя, как после первой проверки С-программы с помошью lint, - несколько обескураженно . Lint - это популярный инструмент проверки раздельно компилируемых частей С-программы на предмет непротиворечивого использования типов [Kernighan, 1984]. Во время опытной эксплуатации типобезопасной компоновки я пытался отслеживать результаты ее работы. С помощью данного средства выявлялись необнаруженные ошибки в каждой из больших програ.мм на С и C+-I-, которые мы пытались откомпилировать и связать. К нашему удивлению, некоторые профаммисты сознательно вносили ошибки в объявления функций, просто чтобы подавить сообщения об ошибках. Например, вызов f (1, а) приводит к ошибке, если f () не объявлена. Я наивно ожидал, что в таком случае профаммист либо добавит правильное объявление функции, либо включит его в заголовочный файл. Оказалось, что была даже третья возможность - просто по.местить любое объявление, не противоречащее вызову: void g() { void f(int ...); чтобы подавить сообщение об ошибке ... f(1,а); Типобезопасная компоновка выдает сообщение об ошибке, если объявление не соответствует определению. Также была обнаружена проблема переносимости. Многие объявляли библиотечные функции пря.мо в коде, вместо того чтобы включить нужный заголовочный файл. Полагаю, что это делалось для уменьшения времени компиляции, но в результате при переносе на другую систе.му объявление оказывалось неверным. Типобезопасная компоновка позволила нам выявить целый ряд таких проблем переносимости (в основном между UNIX System V и BSD UNIX). Прежде чем остановиться на той схеме, которая включена в язык, был рассмотрен ряд других возможностей [Stroustrup, 1988]: □ не вводить явных директив компоновки, а положиться на инстру.менталь-ные средства при связывании с С-функциями; □ выполнять типобезопасную компоновку и перегрузку только для функций, явно помеченных ключевым словом overload; □ осуществлять типобезопасную компоновку только для функций, которые никак не могли быть С-функциями, поскольку их типы нельзя выразить на С. Опыт использования принятой схемы убедил меня в том, что проблемы, которых я опасался в случае выбора альтернативного решения, были вполне реальными. Например, распространение контроля на все функции стало благом, а программирование на смеси С и C-t-+ оказалось настолько популярным, что любое усложнение совместной компоновки было бы воспринято болезненно. Две особенности вызывали со стороны пользователей нарекания, которые не утихли до сих пор. В первом случае я считаю, что мы были правы, а относительно второго не уверен. Функция, объявленная как имеющая С-компоновку, по-прежнему обладает семантикой вызова, принятой в С++. Это значит, что формальные аргументы должны быть объявлены, а фактические - соответствовать им с учетом правил сопоставления и разрешения неоднозначности, действующих в С++. Некоторые пользователи хотели бы, чтобы функции с С-ко.мпоновкой подчинялись правилам вызова С. В таком случае можно было упростить использование заголовочных файлов С. Но это же позволило бы небрежным программистам вернуться к ослабленному контролю типов, характерному для С. Еще один аргумент против специальных правил для С связан с тем, что другие программисты высказывали такие же просьбы для компоновки с Pascal, Fortran и PL/I с поддержкой соответствующих правил вызова. Напри.мер, для функций с Pascal-компоновкой предлагалось неявно преобразовывать С-строки в Pascal-строки; для функций с Fortran-компоновкой - реализовать вызов по ссылке и добавлять информацию о типе массива и т.д. Если бы мы предоставили специальные возможности для С, то были бы обязаны наделить компилятор С-ы- знанием соглашений о вызове, принятых в огромном количестве языков. Было правильным воспротивиться такому давлению, хотя включение подобных дополнительных услуг и помогло бы отдсльны.м программистам, работающим на смеси языков. Располагая только лишь семантикой С++, многие обнаружили, что для построения интерфейсов с таки.ми языками, как Pascal и Fortran, где поддерживается передача аргументов по ссылке, полезны ссылки С++ (см. раздел 3.7). С другой стороны, акцентирование внимания только на компоновке породило определенную сложность. В нашем решении прямо не рассматривались проблемы среды, поддерживающей программирование на смеси языков, и указатели на функции с разны.ми соглашениями о вызове. Пользуясь правилами ко.мнонов-ки С++, мы можем непосредственно выразить, каки.м соглашениям о вызове подчиняется написанная на С или С++ функция. Но нельзя просто сказать, что са.ма функция подчиняется соглашениям С++, а ее аргументы - соглашениям С. Можно выразить это косвенно [ARM, стр. 118], например: typedef void (*PV)(void*,void*); void* sortKvoid*, unsigned, PV) ; extern C void* sort2(void*, unsigned, PV); Здесь sortl 0 имеет С++-компоновку, принимает указатель на функцию с С++-К0МП0Н0ВК0Й; sort2 () имеет С-компоновку, принимает указатель на функцию с С++-К0МП0Н0ВК0Й. Это простые случаи. Другой пример: extern С typedef void (*CPV)(void*,void*); void* sort3(void*, unsigned, CPV); extern C void* sort4(void*, unsigned, CPV); Здесь sorts () имеет С++-компоновку и принимает указатель на функцию с С-ко.мпоновкой; sort 4 () имеет С-компоновку и принимает указатель на функцию с С-компоновкой. Это почти предел того, что можно выразить в языке. Альтернативы тоже не очень удачны: можно либо ввести соглашения о вызове в систему типов, либо использовать при вызове переходники для преобразования одних соглашений в другие. Компоновка, межъязыковые вызовы и передача объектов из одного языка в другой - это непростые проблемы, у них много аспектов, зависящих от реализации. В данной области правила меняются по мере возникновения новых языков, аппаратных архитектур и методов реализации.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |