встроенная функция в разных единицах перевода с разными флагами компилятора неопределенное поведение?

В Visual Studio вы можете установить различные параметры компилятора для отдельных файлов cpp. например: в разделе «генерация кода» мы можем включить базовые проверки во время выполнения в режиме отладки. или мы можем изменить модель с плавающей запятой (точная / строгая / быстрая). это всего лишь примеры. Есть много разных флагов.

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

мы действительно так быстро в стране неопределенного поведения? или компиляторы будут обрабатывать эти случаи?

6

Решение

Что касается Стандарта, то каждая комбинация флагов командной строки превращает компилятор в отдельную реализацию. Хотя для реализаций полезно иметь возможность использовать объектные файлы, созданные другими реализациями, Стандарт не предъявляет к ним никаких требований.

Даже при отсутствии встраивания рассмотрите возможность использования следующей функции в одном модуле компиляции:

char foo(void) { return 255; }

и следующее в другом:

char foo(void);
int arr[128];
void bar(void)
{
int x=foo();
if (x >= 0 && x < 128)
arr[x]=1;
}

Если char был тип со знаком в обеих единицах компиляции, значение x во втором блоке будет меньше нуля (таким образом пропуская присвоение массива). Если бы это был неподписанный тип в обеих единицах, он был бы больше 127 (аналогично, пропуская назначение). Если один модуль компиляции использовал подписанный char а другой использовал unsigned, однако, и если реализация ожидала возвращаемые значения с расширенным знаком или с нулевым расширением в регистре результатов, результатом может быть то, что компилятор может определить, что x не может быть больше 127, даже если оно содержит 255, или что оно не может быть меньше 0, даже если оно содержит -1. Следовательно, сгенерированный код может получить доступ arr[255] или же arr[-1]с потенциально катастрофическими результатами.

Несмотря на то, что во многих случаях было бы безопасно комбинировать код с использованием разных флагов компилятора, в стандарте не предпринимается никаких попыток отличить те, где такое смешивание безопасно, от тех, где оно небезопасно.

2

Другие решения

встроенная функция может быть определена несколько раз в программе, как
Пока определения идентичны

Нет. («Идентичный» здесь даже не является четко определенной концепцией.)

Формально определения должны быть эквивалентны в каком-то очень сильном смысле, который даже не имеет смысла как требование и о котором никто не заботится:

// in some header (included in multiple TU):

const int limit_max = 200; // implicitly static

inline bool check_limit(int i) {
return i<=limit_max; // OK
}

inline int impose_limit(int i) {
return std::min(i, limit_max); // ODR violation
}

Такой код вполне разумен, но формально нарушает одно правило определения:

в каждом определении D, соответствующие имена, смотрел в соответствии с
6.4 [basic.lookup] должен ссылаться на объект, определенный в определении D, или должен ссылаться на тот же объект после перегрузки
разрешение (16.3 [over.match]) и после сопоставления частичного шаблона
специализация (17.9.3 [temp.over]), за исключением того, что имя может относиться к
константный объект с внутренней связью или без нее, если объект имеет такую ​​же
литеральный тип во всех определениях D, и объект инициализируется
с постоянным выражением (8.20 [expr.const]), и значение (но не
адрес) объекта используется
, и объект имеет то же значение
во всех определениях D;

Поскольку исключение не позволяет использовать объект const с внутренней связью ( const int является неявно статическим) с целью прямой привязки константной ссылки (и затем использования ссылки только для ее значения). Правильная версия:

inline int impose_limit(int i) {
return std::min(i, +limit_max); // OK
}

Здесь значение limit_max используется в унарном операторе + и тогда константная ссылка привязывается к временному, инициализированному с этим значением. Кто на самом деле это делает?

Но даже комитет не верит, что формальные ODR имеют значение, как мы можем видеть в Основной выпуск 1511:

1511. const volatile переменные и правило одного определения

Раздел: 6.2 [basic.def.odr] Статус: CD3 Отправитель: Ричард
Смит Дата: 2012-06-18

[Перенесено в ДР на совещании в апреле 2013 года.]

Эта формулировка, возможно, недостаточно ясна для примера, такого как:

  const volatile int n = 0;
inline int get() { return n; }

Мы видим, что Комитет считает, что это вопиющее нарушение намерения и цели УСО, как написано, код, который читает различные изменчивые объекты в каждом TU, то есть код, который имеет видимый побочный эффект на разные объект, так что разные видимый побочный эффект, все в порядке, потому что мы делаем не забота, которая есть какая.

Важным является то, что эффект встроенной функции является неопределенно эквивалентным: выполнение изменяемого чтения int, что является очень слабой эквивалентностью, но достаточной для естественное использование ОДР, который является безразличие экземпляра: какой конкретный экземпляр встроенной функции не имеет значения и не может иметь значения.

В частности, значение, считываемое с помощью volatile read, по определению не известно компилятору, поэтому условие post и инварианты этой функции, проанализированные компилятором, одинаковы.

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

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

Поскольку «стандарт» (который на самом деле не является спецификацией языка программирования) позволяет объектам с плавающей запятой иметь реальное представление, не допускаемое их официально объявленным типом, абсолютно без ограничений, используя любой энергонезависимый квалифицированный тип с плавающей запятой в что-либо многократно определенное с учетом ODR кажется проблематичным, если вы не активируете «double средства double«режим (который является единственным нормальным режимом).

0

По вопросам рекламы [email protected]