Почему компоновщик g++ не предупреждает об этом несовместимом объявлении функции?
Это было протестировано на Debian squeeze с g++ 4.4 и g++ 4.7. Рассмотрим два исходных файла C++.
################
foo.cc
#################
#include <string>
using std::string;
int foo(void)
{
return 0;
}
#################
bar.cc
#################
#include <string>
using std::string;
//int foo(void);
string foo(void);
int main(void)
{
foo();
return 0;
}
##################
Если я скомпилирую и запустлю это, как ожидается, возникнут проблемы Я использую scons.
################################
SConstruct
################################
#!/usr/bin/python
env = Environment(
CXX="g++-4.7",
CXXFLAGS="-Wall -Werror",
#CXX="g++",
#CXXFLAGS="-Wall -Werror",
)
env.Program(target='debug', source=["foo.cc", "bar.cc"])
#################################
Компиляция и запуск...
$ scons
g++-4.7 -o bar.o -c -Wall -Werror bar.cc
g++-4.7 -o foo.o -c -Wall -Werror foo.cc
g++-4.7 -o debug foo.o bar.o
$ ./debug
*** glibc detected *** ./debug: free(): invalid pointer: 0xbff53b8c ***
======= Backtrace: =========
/lib/i686/cmov/libc.so.6(+0x6b381)[0xb7684381]
/lib/i686/cmov/libc.so.6(+0x6cbd8)[0xb7685bd8]
/lib/i686/cmov/libc.so.6(cfree+0x6d)[0xb7688cbd]
/usr/lib/libstdc++.so.6(_ZdlPv+0x1f)[0xb7856c5f]
/lib/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0xb762fca6]
./debug[0x8048461]
======= Memory map: ========
08048000-08049000 r-xp 00000000 fd:10 7602195 /home/faheem/corrmodel/linker/debug
08049000-0804a000 rw-p 00000000 fd:10 7602195 /home/faheem/corrmodel/linker/debug
09ae0000-09b01000 rw-p 00000000 00:00 0 [heap]
b7617000-b7619000 rw-p 00000000 00:00 0
b7619000-b7759000 r-xp 00000000 fd:00 1180005 /lib/i686/cmov/libc-2.11.3.so
b7759000-b775a000 ---p 00140000 fd:00 1180005 /lib/i686/cmov/libc-2.11.3.so
b775a000-b775c000 r--p 00140000 fd:00 1180005 /lib/i686/cmov/libc-2.11.3.so
b775c000-b775d000 rw-p 00142000 fd:00 1180005 /lib/i686/cmov/libc-2.11.3.so
b775d000-b7760000 rw-p 00000000 00:00 0
b7760000-b777c000 r-xp 00000000 fd:00 4653173 /lib/libgcc_s.so.1
b777c000-b777d000 rw-p 0001c000 fd:00 4653173 /lib/libgcc_s.so.1
b777d000-b777e000 rw-p 00000000 00:00 0
b777e000-b77a2000 r-xp 00000000 fd:00 1179967 /lib/i686/cmov/libm-2.11.3.so
b77a2000-b77a3000 r--p 00023000 fd:00 1179967 /lib/i686/cmov/libm-2.11.3.so
b77a3000-b77a4000 rw-p 00024000 fd:00 1179967 /lib/i686/cmov/libm-2.11.3.so
b77a4000-b7889000 r-xp 00000000 fd:00 2484736 /usr/lib/libstdc++.so.6.0.17
b7889000-b788d000 r--p 000e4000 fd:00 2484736 /usr/lib/libstdc++.so.6.0.17
b788d000-b788e000 rw-p 000e8000 fd:00 2484736 /usr/lib/libstdc++.so.6.0.17
b788e000-b7895000 rw-p 00000000 00:00 0
b78ba000-b78bc000 rw-p 00000000 00:00 0
b78bc000-b78bd000 r-xp 00000000 00:00 0 [vdso]
b78bd000-b78d8000 r-xp 00000000 fd:00 639026 /lib/ld-2.11.3.so
b78d8000-b78d9000 r--p 0001b000 fd:00 639026 /lib/ld-2.11.3.so
b78d9000-b78da000 rw-p 0001c000 fd:00 639026 /lib/ld-2.11.3.so
bff41000-bff56000 rw-p 00000000 00:00 0 [stack]
Aborted
Eww. Этого можно было бы избежать, если бы компоновщик предупредил, что foo
был объявлен двумя различными способами. Даже с -Wall
это не так. Итак, есть ли причина, почему это не так, и есть ли какой-нибудь флаг, который я могу включить, чтобы предупредить? Заранее спасибо.
РЕДАКТИРОВАТЬ: Спасибо за все ответы. При наличии конфликтующих определений функций компоновщик выдает предупреждение, а не конфликтующее определение и объявление функций, как в моем примере выше. Я не понимаю причину этого другого поведения.
4 ответа
Компоновщик просто действует на имена, которые, как говорит компилятор, определены в модулях или на которые ссылаются (необходимы) модули. GCC, очевидно, использует "Itanium C++ ABI" для искажения имен функций (начиная с GCC 3). Для большинства функций возвращаемый тип не включен в искаженное имя, поэтому компоновщик его не учитывает:
Типы функций составлены из их типов параметров и, возможно, типа результата. За исключением типа внешнего уровня или внешнего имени, разделенного иным образом, в кодировке функции или, эти типы ограничиваются парой "F..E". Для целей подстановки (см. Сжатие ниже) типы функций с разделителями и неограниченные функции считаются одинаковыми.
То, включает ли искажение тип функции тип возвращаемого значения, зависит от контекста и характера функции. Правила для того, чтобы решить, включен ли тип возвращаемого значения:
- Шаблонные функции (имена или типы) имеют закодированные возвращаемые типы, с исключениями, перечисленными ниже.
- Типы функций, не отображаемые как часть искажения имени функции, например, параметры, типы указателей и т. Д., Имеют закодированный тип возврата, с исключениями, перечисленными ниже.
- Имена не шаблонных функций не имеют закодированных типов возврата.
Исключения, упомянутые в (1) и (2) выше, для которых тип возвращаемого значения никогда не включается,
- Конструкторы.
- Деструкторов.
- Функции оператора преобразования, например, оператор int
В целом, в C++ тип возвращаемого значения функции не учитывается, когда компилятор выполняет поиск по имени (например, для разрешения перегрузки). Это может быть частью причины, по которой тип возвращаемого значения обычно не включается в искажение имени. Я не знаю, есть ли более веская причина не включать возвращаемый тип в искаженное имя.
Компоновщик C++ идентифицирует функции только настолько, насколько это необходимо для уникальной идентификации.
Это из следующей углубленной статьи о компоновщике C++.
... названия символов украшены дополнительными строками. Это называется искажением названия.
Украшение перед именем идентификатора необходимо, потому что C++ поддерживает пространства имен. Например, одно и то же имя функции может встречаться несколько раз в разных пространствах имен, но каждый раз обозначает разные сущности. Чтобы дать возможность компоновщику различать эти сущности, к имени каждого идентификатора добавляется токен, представляющий входящие в него пространства имен.
Декорация после имени идентификатора необходима, потому что C++ допускает перегрузку функций. Опять же, одно и то же имя функции может обозначать разные идентификаторы, которые различаются только в списке параметров. Чтобы дать возможность компоновщику различать их, к имени идентификатора добавляются токены, представляющие список параметров. Тип возврата функции не учитывается, поскольку две перегруженные функции не должны различаться только по типу возврата.
Таким образом, дело в том, что искажение имени, применяемое к функциям, игнорирует тип возвращаемого значения, поскольку перегруженные функции не могут отличаться по типу возвращаемого значения. Таким образом, компоновщик не может определить проблему.
Это лучший пример причины наличия локального файла заголовка проекта (возможно, foobar.h
) который включает в себя все такие функции. Таким образом, компилятор может видеть такие проблемы.
Линкеры никогда не предназначались для выявления такой проблемы. Должен оставить что-то для настоящих инженеров ™.:-)
$ cat foo.cpp
#include <string>
using std::string;
int foo(void)
{
return 0;
}
$ cat bar.cpp
#include <string>
using std::string;
//int foo(void);
string foo(void);
int main(void)
{
foo();
return 0;
}
$ g++ -c -o bar.o bar.cpp
$ g++ -c -o foo.o foo.cpp
$ g++ foo.o bar.o
$ ./a.out
$ echo $?
0
$ g++ --version
g++ (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1
Не может воспроизвести.