$ g++ --version
Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/4.2.1
Apple clang version 12.0.0 (clang-1200.0.32.29)
Target: x86_64-apple-darwin23.4.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
а.cc
#include<iostream>
using namespace std;
static int x = 5053;
void f2();
int main() {
cout << "a: " << x << endl;
f2();
return 0;
}
b.cc
#include<iostream>
using namespace std;
static int x = 4921;
string f2() {
cout << "b: " << x << endl;
return "";
}
Выход
$ g++ --std=c++17 a.cc b.cc && ./a.out
a: 5053
b: 4921
Почему я смог переслать объявление string f2(); из b.cc как void f2(); в a.cc?
Будем признательны за любые ссылки на cppreference или спецификации, позволяющие это сделать.
Включите все предупреждения. Вероятно, происходит следующее: вызывается первое доступное совпадение, которое оказывается string f2, потому что void f2 не определено.
g++ --std=c++17 a.cpp b.cpp && ./a.out /usr/bin/ld: /tmp/ccZVfIKp.o: in function main': a.cpp:(.text+0x47): неопределенная ссылка на f2()' collect2: error: ld returned 1 exit status Вот что я получил
@kesarlingHe-Him <br> cat a.cc | grep extern <br> extern void f2();. g++ -Wall --std=c++17 a.cc b.cc && ./a.out <br> a: 5053 b: 4921
Как вы думаете, что делает cat a.cc | grep extern extern void f2();?
Программа здесь не работает. Получаем ошибку компоновщика.
@kesarlingHe-Him Функции уже есть extern. Не нужно писать extern void f2();
@ user12002570, я очень надеюсь, что ты не говоришь, что string f2 — это перегрузка void f2.
@user12002570 если это так, то объясните это
@kesarlingHe-Him Мы действительно должны получить ошибку компоновщика, как показано в демо . Тем не менее обратите внимание, что вы можете не писать extern здесь с помощью f2
@kesarlingHe-Him Просто пытался показать, что добавление extern не привело для меня к каким-либо предупреждениям/ошибкам с g++ -Wall. Однако, кстати, с -Weverything, независимо от наличия extern в предварительном объявлении в a.cc, я получаю -Wmissing-prototypes, т. е. b.cc:7:8: warning: no previous prototype for function 'f2' [-Wmissing-prototypes] .
@ps_ И gcc, и clang выдают ошибку компоновщика. Демо
@ps_, вот и все! Я думаю это как-то связано с ОС. Некоторые ОС добавляют нестандартные функции, ведущие к ужасным практикам кодирования (помните int a[n]; или #include <bits/stdc++.h>?)
@user12002570 user12002570 «Мы действительно должны получить ошибку компоновщика». Нет, не должны. Возможно, нам так повезет, что иногда, в очень особых обстоятельствах, мы получаем ошибку компоновщика.
@n.m.couldbeanAI Да, я знаю, что это не ошибка. Почему-то забыл, что здесь диагностика не требуется. Я обновил ответ, чтобы отразить то же самое. Спасибо, что напомнили мне удалить часть «это ошибка».





Будем признательны за любые ссылки на cppreference или спецификации, позволяющие это сделать.
Программа имеет неопределенное поведение, поскольку вы использовали f2, но не предоставили никакого определения для версии void f2();. По сути, нам необходимо предоставить одно и только одно определение невстроенной функции, используемой в odr.
Это видно из ODR:
Во всей программе (включая любые стандартные и пользовательские библиотеки) должно присутствовать одно и только одно определение каждой невстроенной функции или переменной, используемой odr (см. ниже). Компилятор не обязан диагностировать это нарушение, но поведение программы, которая его нарушает, не определено.
Как видите, реализация для этого не обязана выдавать диагностику.
В опубликованной демонстрации в main.cpp, если я изменю предварительное объявление на string f2();, оно все равно выдаст ошибку компоновщика: godbolt.org/z/TxcKPYb54. Тем не менее, g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 выдает ошибку компоновщика, которая исправляется, если я изменяю предварительное объявление, чтобы оно имело правильный тип возвращаемого значения. Итак, похоже на проблему с моей версией яблочного звона. Не уверен, исправлено ли это в новых версиях Apple Clang.
@ps_ Если вы измените void f2() на string f2() в main.cpp, это начнет работать. См. модифицированную демонстрацию, где мы не получаем ошибок компоновщика. Должно быть, какая-то проблема с настройками Godbolt.
@ps_ См. обновленный ответ, где я добавил ссылку на cppreference, которая объясняет, что это неопределенное поведение, поскольку вы должны предоставить одно и только одно определение невстроенной функции, которая используется odr. Нарушение приводит к UB и компилятор не обязан его диагностировать.
Это не ошибка. Поведение неопределенное, поэтому может случиться что угодно. Вызов «неправильной» функции попадает в область «чего угодно». Ваш пример с godbolt не удался, потому что libstdc++ обрабатывает std::string особым образом. Измените string на vector<char> и посмотрите, что произойдет, или используйте libc++ и посмотрите, что произойдет.
Я думаю, что вид нарушений ODR - это неправильно сформированный отчет о недоставке?
@HolyBlackCat это действительно так.
@n.m.couldbeanAI Да, когда я обновил ответ, указав, что это UB (или IFNDR), я забыл удалить из ответа часть «это ошибка».
Мы не всегда получаем ошибку компоновщика, поскольку стандарт C++ не требует ошибки компоновщика или любой другой ошибки в этом случае, а реализации в большинстве случаев не делают ничего лишнего.
Стандарт не требует наличия ошибок, поскольку общая технология компоновщика не позволяет реализации обнаруживать такие ошибки.
Страуструп решил исключить из C++ искажение имен тип возвращаемого значения, чтобы имена двух функций, которые различаются только типом возвращаемого значения, преобразулись в одно и то же имя символа. Благодаря этому некоторые ошибки, связанные с нарушением ODR, остаются незамеченными. Если заставить возвращаемый тип участвовать в искажении, другие ошибки нарушения ODR останутся незамеченными.
Невозможно обеспечить обнаружение всех ошибок, связанных с нарушением ODR, с помощью существующих общедоступных компоновщиков. Таким образом, стандарт, по сути, позволяет реализации делать то, что делал оригинальный компилятор Страуструпа: исключать возвращаемый тип из искажения имени.
Компилятор, который вы используете, делает именно это. Итак, это что-то вроде _Z2f2v в объектном файле. Вы можете увидеть это, набрав nm a.o | grep f2 и nm b.o | grep f2 или посмотрев выходные данные сборки в проводнике компилятора (используйте clang, добавьте -stdlib=libc++ к параметрам и снимите флажок «снять символы» в меню). Нигде нет указаний на то, что функция должна возвращать какой-либо конкретный тип.
Так почему же тогда gcc обнаруживает эту ошибку?
Это потому, что иногда gcc включает тип возвращаемого значения в искажение имени, и ваша программа просто попадает в этот особый случай. Это связано с большой поломкой ABI в C++11. gcc и libstdc++ пришлось изменить макет некоторых классов стандартной библиотеки, в частности std::string (также std::list, но эй, кто это использует?). Итак, авторы gcc сделали умную вещь, чтобы сохранить обратную совместимость: они изменили искажение всего, что включает в себя эти классы - -- и на этот раз и в возвращаемом типе. Таким образом, старый код ABI не может без ошибок связываться с новым кодом ABI. Имена не будут совпадать.
Вы можете убедиться в этом, вызвав nm для объектов, скомпилированных с помощью g++ , или снова зайдя в проводник компилятора . Вы увидите, что объект, который упоминает void f2(), по-прежнему содержит что-то вроде _Z2f2v, но объект, который упоминает string f2(), имеет что-то вроде _Z2f2B5cxx11v (обратите внимание на cxx11 в имени — таким образом мы знаем, что имеем дело с новым пост-C++). 11 ПАБИ). Все бы хорошо, но если вы используете обе функции в одной и той же программе (в разных единицах трансляции), компилятор этого не обнаружит, что является еще одним необнаружимым нарушением ODR (все еще разрешенным стандартом).
Этого не происходит с другими типами, поэтому, если вы измените f2 на возврат, скажем, std::vector<char>, вы не получите ошибку компоновщика ни с одним компилятором.
попробуй сделать
extern void f2();