我有一个使用C bison解析器的C++项目.C语法分析器使用函数指针的结构来调用函数,这些函数在生产被bison减少时创建正确的AST节点:
typedef void Node;
struct Actions {
Node *(*newIntLit)(int val);
Node *(*newAsgnExpr)(Node *left, Node *right);
/* ... */
};
Run Code Online (Sandbox Code Playgroud)
现在,在项目的C++部分,我填写了这些指针
class AstNode {
/* ... */
};
class IntLit : public AstNode {
/* ... */
};
extern "C" {
Node *newIntLit(int val) {
return (Node*)new IntLit(val);
}
/* ... */
}
Actions createActions() {
Actions a;
a.newIntLit = &newIntLit;
/* ... */
return a;
}
Run Code Online (Sandbox Code Playgroud)
现在我把它们放在其中的唯一原因extern "C"是因为我希望它们具有C调用约定.但最佳的是,我希望他们的名字仍然受到损害.它们永远不会从C代码中调用,因此名称重整不是问题.使它们受损会避免名称冲突,因为有些动作被称为error,并且C++回调函数具有丑陋的名称,如下所示只是为了避免与其他模块的名称冲突.
extern "C" {
void uglyNameError(char const *str) …Run Code Online (Sandbox Code Playgroud) 可能的重复:
使用 C++ 类成员函数作为 C 回调函数
我正在使用 C 库 (winpcap) 编写面向对象的库。我需要传递网络数据包到达时调用的回调函数作为函数指针。我想将成员函数指针传递给 winpcap,以保持我的设计面向对象并允许不同的对象接收不同的数据包。然而,据我所知,成员函数有不同的调用约定,因此不能传递给 C 函数。有没有办法来解决这个问题。我对 boost::bind 的实验(除了反复试验之外,我几乎无法使用它)并没有结果。
有没有办法改变成员函数的调用约定?
这是我现在使用的回调函数的定义和实际传递给winpcap的
void pcapCallback( byte* param, const struct pcap_pkthdr* header, const byte* pkt_data );
pcap_loop( adhandle, 0, pcapCallback, NULL );
Run Code Online (Sandbox Code Playgroud)
pcap_loop 只采用函数的名称(目前在全局范围内)。这是函数指针参数(pcap_loop 的第三个参数)的定义。由于这是第三方代码,我无法真正改变它。我必须有一个可以采用这种形式的成员函数:
typedef void (*pcap_handler)(u_char *, const struct pcap_pkthdr *, const u_char *);
Run Code Online (Sandbox Code Playgroud)
据我了解,成员函数将使用 thiscall 而 c 函数指针需要一个 cdecl
我想将参数从 C 传递给汇编函数。
在类 UNIX 系统上,前六个参数为 rdi、rsi、rdx、rcx、r8 和 r9。
在 Windows 上,前四个参数为 rcx、rdx、r8 和 r9。
现在,我的问题是:在 BIOS 或 DOS 编程级别上,哪些寄存器接收这些参数?如果参数数量超过6个,如何编写程序集来处理这些参数?
我的目标是使用__cdecl和__stdcall调用约定轻松提取任意函数的原型.它在32位工作正常.唯一改变的是模板函数参数中的调用约定.
根据维基百科的说法: "在Windows环境中编译x64架构时(无论是使用Microsoft还是非Microsoft工具),只有一个调用约定 - 这里描述的那个,所以stdcall,thiscall,cdecl,fastcall等. ,现在都是一样的."
这打破了64位的代码.即使调用约定相同,将函数作为参数传递仍然需要使用正确的命名法.IE如果函数定义为__stdcall,则必须将其传递给接受__stdcall的包装器.即使__cdecl相同,您仍然必须将定义为__cdecl的函数传递给接受__cdecl的包装器.
以32位工作的示例:
template<typename T, typename... Args>
struct WrapperSTD { typedef T(__stdcall *Functor)(Args...); };
template<typename T, typename... Args>
struct WrapperC { typedef T(*Functor)(Args...); };
template<typename T, typename... Args>
WrapperSTD<T, Args...> wrap(T(__stdcall *func)(Args...)) {
return WrapperSTD<T, Args...>{};
}
template<typename T, typename... Args>
WrapperC<T, Args...> wrap(T(*func)(Args...)) {
return WrapperC<T, Args...>{};
}
Run Code Online (Sandbox Code Playgroud)
我的目标是能够运行,例如:
using MsgBoxProto = decltype(wrap(MessageBoxA))::Functor;
Run Code Online (Sandbox Code Playgroud)
这适用于32位.但是,由于__stdcall和__cdecl在x64中显然是相同的,因此它不能在64位中工作并引发错误,表示调用是不明确的.它还告诉我模板已经定义.直观地说,似乎我能够将带有__cdecl的函数传递给这个__stdcall函数,因为编译器认为它们是相同的.但是,这不起作用:
template<typename T, typename... Args>
struct WrapperFC { typedef T(__stdcall *Functor)(Args...); };
template<typename T, typename... Args>
WrapperFC<T, Args...> wrap(T(__stdcall …Run Code Online (Sandbox Code Playgroud) va_arg\ va_start\ va_list\va_end宏在 x64 中如何工作?
i386 中的调用约定在堆栈上传递参数,因此宏只是增加一些指向堆栈基址的指针并转发它。然而,在 x64 中,所有参数都是通过寄存器传递的......那么那里会发生什么呢?被调用函数如何知道哪些寄存器用于传递参数以确保它不会破坏它们?
我最近做了很多x64汇编编程(在Linux上),用于与我的C/C++程序集成.
由于我主要关心效率,我喜欢尽可能少地使用不同的寄存器/存储器地址,以及尝试不创建任何堆栈帧或保留寄存器(每个周期计数).
根据cdecl r10和r11寄存器不保留,我希望在我的函数中使用它们作为临时变量,最好不要保留.它是否会导致任何编译器出现任何无法解决的问题/错误(到目前为止还没有遇到任何问题,但这是一个问题)?
我不明白不在 RAX 中传递参数有什么好处,因为返回值在 RAX 中,无论如何它都会被被调用者破坏。
有人可以解释一下吗?
在某些情况下,我们使用标签来区分函数。标签通常是一个空结构:
struct Tag { };
Run Code Online (Sandbox Code Playgroud)
假设我有一个使用此标签的函数:
void func(Tag, int a);
Run Code Online (Sandbox Code Playgroud)
现在,我们调用这个函数:
func(Tag(), 42);
Run Code Online (Sandbox Code Playgroud)
并检查生成的 x86-64 反汇编,godbolt:
mov edi, 42
jmp func(Tag, int) # TAILCALL
Run Code Online (Sandbox Code Playgroud)
没关系,标签得到了完全优化:没有为其分配寄存器/堆栈空间。
但是,如果我查看其他平台,就会发现该标签存在一些。
在 ARM 上,r0用作标签,并且它被归零(似乎没有必要):
mov r1, #42
mov r0, #0
b func(Tag, int)
Run Code Online (Sandbox Code Playgroud)
对于 MSVC,ecx被用作标记,并且它是从堆栈中“初始化”的(同样,似乎没有必要):
movzx ecx, BYTE PTR $T1[rsp]
mov edx, 42 ; 0000002aH
jmp void func(Tag,int) ; func
Run Code Online (Sandbox Code Playgroud)
我的问题是:是否有一种标签技术在所有这些平台上都得到了同样的优化?
注意:我没有找到 SysV ABI 指定可以在参数传递时优化空类的地方...(甚至,Itanium C++ ABI说:“空类的传递与普通类没有什么不同”。)
在 RISC-V 伪内核 (pk) 或 Linux 下运行的程序中系统调用的调用约定是什么?
查看由 riscv-gnu-toolchain 生成的代码,规则似乎是:
a7a0到a50a0是这个吗?
真的有必要将未使用的参数归零吗?
注册a6呢?这可以用于另一个 sycall 参数吗?
调用exit()系统调用的示例:
li a0, 1 # argument that is used by the syscall
li a1, 0 # unused arguments
li a2, 0
li a3, 0
li a4, 0
li a5, 0
li a7, 93 # exit syscall number
Run Code Online (Sandbox Code Playgroud)