FunctionType: почему построенная функция отличается от оригинала? [Дубликат]

Что такое «неопределенный ссылочный / неразрешенный внешний символ»

Я попытаюсь объяснить, что такое «неопределенный ссылочный / неразрешенный внешний символ».

note : я использую g ++ и Linux, и все примеры для него

Например, у нас есть некоторый код

// 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, мы взломаем его:)

Итак, в результате - «неопределенная ссылка / неразрешенная внешняя ошибка символа» происходит, когда компоновщик не может найти глобальные символы в объектных файлах.

10
задан Josh Caswell 19 November 2013 в 00:17
поделиться

2 ответа

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

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


Наоборот, вы можете просто хотеть знать имеют ли две функции ту же самую реализацию. Для этого ответ Martijn Pieters является очевидной отправной точкой и, возможно, даже конечной точкой (в зависимости от того, заботитесь ли вы о замыканиях, глобальных шагах и т. Д.).


Но то, что вы просили, это что-то отличные от любого из них; вы, видимо, хотите просмотреть код вручную, чтобы увидеть «как это работает»:

Другим решением может быть некоторый метод, который я могу запустить для функции, чтобы увидеть, что она содержит или как она работает. Таким образом, вид (lambda x: x) .what (), который вернет способ работы, может быть, в словаре или что-то в этом роде.

Эта функция уже существует: 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).

7
ответ дан abarnert 24 August 2018 в 20:26
поделиться

Единственное, что вы можете проверить, это равенство кода:

>>> x = lambda x: x
>>> y = lambda y: y
>>> x.__code__.co_code
'|\x00\x00S'
>>> x.__code__.co_code == y.__code__.co_code
True

Здесь байт-код для обеих функций одинаковый. Вам, возможно, понадобится проверить больше аспектов объектов кода (постоянные и замыкания приходят в голову), но равный байт-код должен равняться одному и тому же пути выполнения.

Есть, конечно, способы создания функций, которые возвращают одинаковое значение для одного и того же входа, но с другим байт-кодом; всегда есть много способов скрыть рыбу.

24
ответ дан Martijn Pieters 24 August 2018 в 20:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: