Безопасно ли связывать объекты gcc 6, gcc 7 и gcc 8?

Безопасно ли связывать объекты С++ 17, С++ 14 и С++ 11 о связывании объектов, скомпилированных с использованием различных языковых стандартов, и Джонатан Уэйкли, превосходный ответ на этот вопрос, объясняет обещания стабильности ABI, которые gcc/libstdc++ сделать, чтобы заверить, что это работает.

Есть еще одна вещь, которая может меняться между версиями gcc - язык ABI через -fabi-version. Допустим, для простоты у меня есть три объектных файла:

  • foo.o, скомпилированный с gcc 6.5 С++ 14
  • bar.o, скомпилированный с gcc 7.4 С++ 14
  • quux.o, скомпилированный с gcc 8.3 С++ 17

Все с соответствующими языками ABI по умолчанию (то есть 10, 11 и 13). Связывание этих объектов безопасно с точки зрения библиотеки согласно связанному ответу. Но есть ли вещи, которые могут пойти не так с точки зрения языка ABI? Есть ли что-то, за чем я должен следить? Кажется, что большинство изменений языка ABI не вызовут проблем, но изменение соглашения о вызовах для пустых типов классов в 12 может?

Ответы

Ответ 1

Кажется, что большинство изменений языка ABI не вызовут проблем, но изменение соглашения о вызовах для пустых типов классов в 12 может?

Изменение соглашения о вызовах для пустых классов может вызвать проблему на x86-64. Вот пример:

def.hpp:

struct Empty { };

struct Foo {
    char dummy[16];
    int a;

    Foo() : a(42) { }
};

void fn(Empty, Foo);

one.cpp:

#include "def.hpp"

int main() {
    fn(Empty(), Foo());
}

two.cpp:

#include <stdio.h>
#include "def.hpp"

void fn(Empty e, Foo foo) {
    printf("%d\n", foo.a);
}

Теперь, если вы скомпилируете их с G++ 8 с разными ABI 11 и 12, например:

g++ -c -fabi-version=11 one.cpp
g++ -c -fabi-version=12 two.cpp
g++ one.o two.o

результирующий a.out не напечатает ожидаемое 42.

Причина в том, что старый ABI (11) резервирует место для Empty() в стеке, а новый ABI (12) - нет. Таким образом, адрес foo будет отличаться между вызывающей стороной и стороной вызываемой стороны.

(Примечание: я включил Foo::dummy чтобы Foo передавался с использованием стека вместо регистров. Если бы Foo передавался с использованием регистров, проблем не было бы.)

Ответ 2

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

изменение соглашения о вызовах для пустых типов классов в 12 может?

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