host.cpp имеет:
int main (void)
{
void * th = dlopen("./p1.so", RTLD_LAZY);
void * fu = dlsym(th, "fu");
((void(*)(int, const char*)) fu)(2, "rofl");
return 0;
}
И p1.cpp имеет:
#include <iostream>
extern "C" bool fu (float * lol)
{
std::cout << "fuuuuuuuu!!!\n";
return true;
}
(Я намеренно оставил ошибки проверены)
При выполнении хоста «fuuuuuuuu !!!» печатается правильно, хотя я типизировал пустой указатель на символ с совершенно другой сигнатурой функции.
Почему это произошло и соответствует ли это поведение между различными компиляторами?
Потому что нет никакой информации о сигнатуре функции в пустом указателе. Или любую информацию, кроме адреса. У вас могут возникнуть проблемы, если вы начнете использовать параметры, хотя.
Это произошло потому, что UB, и это поведение не соответствует ни с чем, вообще, по любой причине.
На самом деле это не очень хороший пример создания дела, которое потерпит неудачу, так как:
fu
,fu
имеет меньше аргументов (или сам фрейм активации имеет меньший объем памяти), чем тип указателя на функцию, к которому вы обращаетесь, так что вы никогда не попадете в ситуацию, когда fu
пытается получить доступ к памяти вне ее настройки записи активации вызывающим абонентом.В конце концов, то, что вы делаете, по-прежнему остается неопределенным поведением, но вы ничего не делаете, чтобы создать нарушение, которое может вызвать проблемы, поэтому оно заканчивается как тихая ошибка.
это поведение согласовано между различными компиляторами?
Нет. Если ваша платформа / компилятор использовал соглашение о вызовах, которое требовало, чтобы вызываемый объект очищал стек, то, к сожалению, вы, скорее всего, спрятались, если есть несоответствие размера записи активации между тем, что называют вызывающим и вызывающим ожидайте … по возвращении вызываемого, указатель стека будет перемещен в неправильное место, возможно, повредит стек и полностью испортит любую относительную адресацию указателя стека.
Просто так получилось, что
C
использования cdecl
преобразование вызова (чтобы вызывающая сторона очищала стек)так что ваш звонок кажется работать правильно.
Но на самом деле поведение не определено. Изменение подписи или использование аргументов вызовет сбой вашей программы:
Например, рассмотрим stdcall
преобразование вызова, где вызываемый Мачта очистить стек. В этом случае, даже если вы объявите правильное преобразование вызовов как для вызывающей, так и для вызываемой стороны, ваша программа все равно будет аварийно завершена, поскольку ваш стек будет поврежден, поскольку вызываемая сторона очистит его в соответствии с его сигнатурой, а заполнитель вызывающей стороны — с другой сигнатурой:
#include <iostream>
#include <string>
extern "C" __attribute__((stdcall)) __attribute__((noinline)) bool fu (float * lol)
{
std::cout << "fuuuuuuuu!!!\n";
return true;
}
void x()
{
(( __attribute__((stdcall)) void(*)(int, const char*)) fu)(2, "rofl");
}
int main (void)
{
void * th = reinterpret_cast<void*>(&fu);
std::string s = "hello";
x();
std::cout << s;
return 0;
}