Как поймать непреднамеренную вставку функции?

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

Пример, приведенный в книге, следующий:

my_source.c

mktemp() { ... }

main() {
  mktemp();
  getwd();
}

Libc

mktemp(){ ... }
getwd(){ ...; mktemp(); ... }

Согласно книге, что происходит в main() в том, что mktemp() (стандартная функция библиотеки C) вставляется реализацией в my_source.c. Хотя имея main() назови мою реализацию mktemp() намеренное поведение, имеющее getwd() (еще одна функция библиотеки C) также вызвать мою реализацию mktemp() не является.

По-видимому, этот пример был реальной ошибкой, существовавшей в версии SunOS 4.0.3 lpr, Книга продолжает объяснять, как исправить было добавить ключевое слово static к определению mktemp() в my_source.c; хотя смена имени в целом должна была исправить и эту проблему.

Эта глава оставляет мне несколько нерешенных вопросов, на которые, я надеюсь, вы, ребята, могли бы ответить:

  1. Есть ли в GCC способ предупредить вставку функции? Мы, конечно, никогда не собираемся делать это, и я хотел бы знать об этом, если это произойдет.
  2. Если наша группа разработчиков программного обеспечения примет практику размещения ключевого слова static перед всеми функциями, которые мы не хотим показывать?
  3. Может ли вставка произойти с функциями, представленными статическими библиотеками?

Спасибо за помощь.

РЕДАКТИРОВАТЬ

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

6 ответов

Решение

Похоже, что вы хотите, чтобы инструменты определяли наличие конфликтов имен в функциях, т. Е. Вы не хотите, чтобы внешне доступные имена функций создавались случайно с одним и тем же именем и, следовательно, "переопределяли" или скрывали функции с одинаковыми имя в библиотеке.

Недавно был задан вопрос SO, связанный с этой проблемой: связывание библиотек с повторяющимися именами классов с помощью GCC

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

Это действительно проблема компоновщика.

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

Когда вы связываете кучу файлов.o вместе, чтобы создать исполняемый файл, компоновщик должен разрешить все эти пропущенные ссылки. Это точка, где может произойти вставка. Если есть неразрешенные ссылки на функцию с именем "mktemp" и несколько библиотек предоставляют открытую функцию с таким именем, какую версию следует использовать? Нет простого ответа на этот вопрос, и да, странные вещи могут случиться, если выбрать неправильный

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

Чисто формально, вставка, которую вы описываете, является прямым нарушением правил определения языка C (правило ODR, на языке C++). Любой достойный компилятор должен либо обнаруживать эти ситуации, либо предоставлять варианты для их обнаружения. Просто запрещается определять более одной функции с одним и тем же именем на языке C, независимо от того, где определены эти функции (стандартная библиотека, другая пользовательская библиотека и т. Д.)

Я понимаю, что многие платформы предоставляют средства для настройки поведения [стандартной] библиотеки, определяя некоторые стандартные функции как слабые символы. Хотя это действительно полезная функция, я считаю, что компиляторы должны по-прежнему предоставлять пользователю средства для обеспечения стандартной диагностики (предпочтительно для каждой функции или для каждой библиотеки).

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

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

Относительно легко написать скрипт, который запускается nm -o на все ваши.o файлы и библиотеки и проверяет, определено ли внешнее имя как в вашей программе, так и в библиотеке. Просто одна из многих здравомыслящих служб, которые не предоставляет компоновщик Unix, потому что он застрял в 1974 году, просматривая по одному файлу за раз. (Попробуйте поместить библиотеки в неправильном порядке и посмотрите, не получите ли вы полезное сообщение об ошибке!)

Если к функции не требуется доступ за пределами файла C, в котором она находится, то да, я бы порекомендовал сделать функцию static,

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

Чередование происходит, когда компоновщик пытается связать отдельные модули. Это не может происходить внутри модуля. Если в модуле есть повторяющиеся символы, компоновщик сообщит об этом как об ошибке.

Для компоновщиков * nix непреднамеренное вставление является проблемой, и компоновщику трудно защититься от него. Для целей этого ответа рассмотрим два этапа связывания:

  1. Компоновщик связывает единицы перевода в модули (в основном приложения или библиотеки).
  2. Компоновщик связывает любые оставшиеся необнаруженные символы путем поиска в модулях.

Рассмотрим сценарий, описанный в "Программирование на Expert C" и вопрос SiegeX. Кулак компоновщика пытается собрать модуль приложения. Это значит, что символ mktemp() является внешним и пытается найти определение функции для символа. Компоновщик находит определение функции в объектном коде модуля приложения и помечает символ как найденный. На этом этапе символ mktemp() полностью разрешен. Это не считается каким-либо предварительным, чтобы учесть возможность того, что другой модуль может определять символ. Во многих отношениях это имеет смысл, поскольку компоновщик должен сначала попытаться разрешить внешние символы в модуле, который он в настоящее время связывает. Это только необнаруженные символы, которые он ищет при связывании в других модулях. Кроме того, поскольку символ был помечен как разрешенный, компоновщик будет использовать приложения mktemp() в любых других случаях, когда необходимо разрешить этот символ. Таким образом, версия приложения mktemp() будет использоваться библиотекой.

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

Для широко используемых модулей создание уникальных имен является проблемой.

Другие вопросы по тегам