Что такое «неопределенный ссылочный / неразрешенный внешний символ»
Я попытаюсь объяснить, что такое «неопределенный ссылочный / неразрешенный внешний символ».
note : я использую g ++ и Linux, и все примеры для него
blockquote>Например, у нас есть некоторый код
// src1.cpp void print(); static int local_var_name; // 'static' makes variable not visible for other modules int global_var_name = 123; int main() { print(); return 0; }
и
// src2.cpp extern "C" int printf (const char*, ...); extern int global_var_name; //extern int local_var_name; void print () { // printf("%d%d\n", global_var_name, local_var_name); printf("%d\n", global_var_name); }
Создание объектных файлов
$ g++ -c src1.cpp -o src1.o $ g++ -c src2.cpp -o src2.o
После фазы ассемблера у нас есть объектный файл, который содержит любые экспортируемые символы. Посмотрите на символы
$ readelf --symbols src1.o Num: Value Size Type Bind Vis Ndx Name 5: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 _ZL14local_var_name # [1] 9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 global_var_name # [2]
Я отклонил некоторые строки из вывода, потому что они не имеют значения
Итак, мы видим следующие символы для экспорта.
[1] - this is our static (local) variable (important - Bind has a type "LOCAL") [2] - this is our global variable
src2.cpp ничего не экспортирует, и мы не видели его символов
Свяжите наши объектные файлы
$ g++ src1.o src2.o -o prog
и запустите его
$ ./prog 123
Linker видит экспортированные символы и связывает их. Теперь мы пытаемся раскомментировать строки в src2.cpp, как здесь
// src2.cpp extern "C" int printf (const char*, ...); extern int global_var_name; extern int local_var_name; void print () { printf("%d%d\n", global_var_name, local_var_name); }
, и перестроить объектный файл
$ g++ -c src2.cpp -o src2.o
OK (нет ошибок), потому что мы только строим объектный файл, связь еще не завершена. Попробуйте установить ссылку
$ g++ src1.o src2.o -o prog src2.o: In function `print()': src2.cpp:(.text+0x6): undefined reference to `local_var_name' collect2: error: ld returned 1 exit status
Это произошло потому, что наше local_var_name статично, то есть оно не отображается для других модулей. Теперь глубже. Получить выход фазы перевода
$ g++ -S src1.cpp -o src1.s // src1.s look src1.s .file "src1.cpp" .local _ZL14local_var_name .comm _ZL14local_var_name,4,4 .globl global_var_name .data .align 4 .type global_var_name, @object .size global_var_name, 4 global_var_name: .long 123 .text .globl main .type main, @function main: ; assembler code, not interesting for us .LFE0: .size main, .-main .ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2" .section .note.GNU-stack,"",@progbits
Итак, мы видели, что для local_var_name нет метки, поэтому линкер не нашел его. Но мы хакеры :), и мы можем это исправить. Откройте src1.s в текстовом редакторе и измените
.local _ZL14local_var_name .comm _ZL14local_var_name,4,4
на
.globl local_var_name .data .align 4 .type local_var_name, @object .size local_var_name, 4 local_var_name: .long 456789
i.e. вам должно быть как ниже
.file "src1.cpp" .globl local_var_name .data .align 4 .type local_var_name, @object .size local_var_name, 4 local_var_name: .long 456789 .globl global_var_name .align 4 .type global_var_name, @object .size global_var_name, 4 global_var_name: .long 123 .text .globl main .type main, @function main: ; ...
мы изменили видимость local_var_name и установили его значение в 456789. Попробуйте построить из него объектный файл
$ g++ -c src1.s -o src2.o
ok, см.
$ readelf --symbols src1.o 8: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 local_var_name
В настоящее время local_var_name имеет привязку GLOBAL (LOCAL)
link
$ g++ src1.o src2.o -o prog
и запускает ее
$ ./prog 123456789
ok, мы взломаем его:)
Итак, в результате - «неопределенная ссылка / неразрешенная внешняя ошибка символа» происходит, когда компоновщик не может найти глобальные символы в объектных файлах.
Если вы действительно хотите знать, будут ли две функции всегда делать то же самое для всех входов, вам придется запускать их как на всех входах (которые будут занимать бесконечное время), так и перехватывать все возможные побочные эффекты (что эффективно невозможно).
Вы могли бы, конечно, придумать несколько эвристик, выбрасывая на них набор разных значений, которые для вашей области приложения, скорее всего, будут генерировать разные выходы, если функции разные. Но очевидно, что для этого не существует универсального решения - в противном случае все модульные тесты будут генерироваться автоматически, что позволит нам полностью работать, не так ли?
Наоборот, вы можете просто хотеть знать имеют ли две функции ту же самую реализацию. Для этого ответ Martijn Pieters является очевидной отправной точкой и, возможно, даже конечной точкой (в зависимости от того, заботитесь ли вы о замыканиях, глобальных шагах и т. Д.).
Но то, что вы просили, это что-то отличные от любого из них; вы, видимо, хотите просмотреть код вручную, чтобы увидеть «как это работает»:
Другим решением может быть некоторый метод, который я могу запустить для функции, чтобы увидеть, что она содержит или как она работает. Таким образом, вид (lambda x: x) .what (), который вернет способ работы, может быть, в словаре или что-то в этом роде.
blockquote>Эта функция уже существует:
dis.dis
. Когда вы запускаете его для функции, он сообщает вам, как работает эта функция. Не в словаре (словаре чего?), А в последовательности строк байт-кода для интерпретатора Python (который является относительно простой стековой машиной с добавлением более высокоуровневого материала сверху, в основном описанным здесь вdis
docs).Или, что еще проще, вы можете получить источник с помощью
inspect.getsource
.Вот как выглядят эти два примера:
>>> f1 = lambda x: x >>> f2 = lambda y: y >>> def f3(z): ... return z >>> dis.dis(f1) 1 0 LOAD_FAST 0 (x) 3 RETURN_VALUE >>> dis.dis(f2) 1 0 LOAD_FAST 0 (y) 3 RETURN_VALUE >>> dis.dis(f3) 1 0 LOAD_FAST 0 (z) 3 RETURN_VALUE >>> inspect.getsource(f1) 'f1 = lambda x: x\n' >>> inspect.getsource(f2) 'f2 = lambda y: y\n' >>> inspect.getsource(f3) 'def f3(z):\n return z\n'
В первом случае вам нужно знать достаточно о
dis
, чтобы понять, что(x)
и т. д. не являются частью байт-кода, а скорее являются частью списка функций локальные имена. (Это объясняется так же в документахinspect
, что и в документахdis
.) Во-вторых, вам нужно знать достаточно о Python, чтобы понять, чтоdef
иlambda
определяют ту же самую функцию , Итак, в любом случае, невозможно автоматизировать это (или, действительно, что-то гораздо большее, чем ответ Martijn).
Единственное, что вы можете проверить, это равенство кода:
>>> x = lambda x: x
>>> y = lambda y: y
>>> x.__code__.co_code
'|\x00\x00S'
>>> x.__code__.co_code == y.__code__.co_code
True
Здесь байт-код для обеих функций одинаковый. Вам, возможно, понадобится проверить больше аспектов объектов кода (постоянные и замыкания приходят в голову), но равный байт-код должен равняться одному и тому же пути выполнения.
Есть, конечно, способы создания функций, которые возвращают одинаковое значение для одного и того же входа, но с другим байт-кодом; всегда есть много способов скрыть рыбу.