Helpex - Trao đổi & giúp đỡ Đăng nhập
74

Có (trong số những người khác) hai loại quy ước gọi - stdcallcdecl . Tôi có một vài câu hỏi về chúng:

  1. Khi một hàm cdecl được gọi, làm thế nào để người gọi biết liệu nó có giải phóng được ngăn xếp không? Tại trang web cuộc gọi, người gọi có biết chức năng đang được gọi là cdecl hay chức năng stdcall không? Làm thế nào nó hoạt động ? Làm thế nào để người gọi biết nếu nó nên giải phóng chồng hay không? Hay đó là trách nhiệm liên kết?
  2. Nếu một hàm được khai báo là stdcall gọi một chức năng (trong đó có một quy ước gọi là cdecl), hoặc theo chiều ngược lại, điều này sẽ là không phù hợp?
  3. Nói chung, chúng ta có thể nói rằng cuộc gọi nào sẽ nhanh hơn - cdecl hoặc stdcall?
74 hữu ích 2 bình luận 92k xem chia sẻ
72

Raymond Chen cho một cái nhìn tổng quan tốt đẹp về những gì __stdcall__cdecllàm .

(1) Người gọi "biết" để dọn dẹp đống sau khi gọi một hàm vì trình biên dịch biết quy ước gọi của chức năng đó và tạo ra mã cần thiết.

void __stdcall StdcallFunc() {}

void __cdecl CdeclFunc()
{
    // The compiler knows that StdcallFunc() uses the __stdcall
    // convention at this point, so it generates the proper binary
    // for stack cleanup.
    StdcallFunc();
}

Có thể không khớp quy ước gọi , như thế này:

LRESULT MyWndProc(HWND hwnd, UINT msg,
    WPARAM wParam, LPARAM lParam);
// ...
// Compiler usually complains but there's this cast here...
windowClass.lpfnWndProc = reinterpret_cast<WNDPROC>(&MyWndProc);

Vì vậy, nhiều mẫu mã nhận được điều này sai, nó thậm chí không buồn cười. Nó được cho là như thế này:

// CALLBACK is #define'd as __stdcall
LRESULT CALLBACK MyWndProc(HWND hwnd, UINT msg
    WPARAM wParam, LPARAM lParam);
// ...
windowClass.lpfnWndProc = &MyWndProc;

Tuy nhiên, giả sử lập trình viên không bỏ qua lỗi trình biên dịch, trình biên dịch sẽ tạo mã cần thiết để dọn sạch ngăn xếp đúng cách vì nó sẽ biết các quy ước gọi của các hàm liên quan.

(2) Cả hai cách nên hoạt động. Trong thực tế, điều này xảy ra khá thường xuyên ít nhất là trong mã tương tác với API Windows, bởi vì __cdeclmặc định cho các chương trình C và C ++ theo trình biên dịch Visual C ++các hàm WinAPI sử dụng __stdcallquy ước .

(3) Không có sự khác biệt hiệu suất thực sự giữa hai.

72 hữu ích 2 bình luận chia sẻ
41

Trong các đối số CDECL được đẩy lên ngăn xếp theo thứ tự đảo ngược, người gọi sẽ xóa ngăn xếp và kết quả được trả về qua sổ đăng ký bộ xử lý (sau này tôi sẽ gọi nó là "đăng ký A"). Trong STDCALL có một điểm khác biệt, người gọi không xóa ngăn xếp, calle làm.

Bạn đang hỏi cái nào nhanh hơn. Không một ai. Bạn nên sử dụng quy ước gọi điện thoại miễn là bạn có thể. Chỉ thay đổi quy ước nếu không có lối thoát, khi sử dụng các thư viện bên ngoài yêu cầu sử dụng quy ước nhất định.

Ngoài ra, có một số quy ước khác mà trình biên dịch có thể chọn làm mặc định, tức là trình biên dịch Visual C ++ sử dụng FASTCALL nhanh hơn về mặt lý thuyết vì sử dụng rộng rãi hơn các thanh ghi bộ xử lý.

Thông thường, bạn phải cung cấp chữ ký quy ước gọi phù hợp cho các hàm gọi lại được chuyển đến một số thư viện bên ngoài, tức là gọi lại qsorttừ thư viện C phải là CDECL (nếu trình biên dịch theo mặc định sử dụng quy ước khác thì chúng ta phải đánh dấu gọi lại là CDECL) hoặc các cuộc gọi lại WinAPI khác phải STDCALL (toàn bộ WinAPI là STDCALL).

Trường hợp thông thường khác có thể là khi bạn đang lưu trữ các con trỏ tới một số hàm bên ngoài, tức là để tạo một con trỏ tới hàm WinAPI, định nghĩa kiểu của nó phải được đánh dấu bằng STDCALL.

Và dưới đây là một ví dụ cho thấy trình biên dịch thực hiện nó như thế nào:

/* 1. calling function in C++ */
i = Function(x, y, z);

/* 2. function body in C++ */
int Function(int a, int b, int c) { return a + b + c; }

CDECL:

/* 1. calling CDECL 'Function' in pseudo-assembler (similar to what the compiler outputs) */
push on the stack a copy of 'z', then a copy of 'y', then a copy of 'x'
call (jump to function body, after function is finished it will jump back here, the address where to jump back is in registers)
move contents of register A to 'i' variable
pop all from the stack that we have pushed (copy of x, y and z)

/* 2. CDECL 'Function' body in pseudo-assembler */
/* Now copies of 'a', 'b' and 'c' variables are pushed onto the stack */
copy 'a' (from stack) to register A
copy 'b' (from stack) to register B
add A and B, store result in A
copy 'c' (from stack) to register B
add A and B, store result in A
jump back to caller code (a, b and c still on the stack, the result is in register A)

STDCALL:

/* 1. calling STDCALL in pseudo-assembler (similar to what the compiler outputs) */
push on the stack a copy of 'z', then a copy of 'y', then a copy of 'x'
call
move contents of register A to 'i' variable

/* 2. STDCALL 'Function' body in pseaudo-assembler */
pop 'a' from stack to register A
pop 'b' from stack to register B
add A and B, store result in A
pop 'c' from stack to register B
add A and B, store result in A
jump back to caller code (a, b and c are no more on the stack, result in register A)
41 hữu ích 4 bình luận chia sẻ
14

Tôi nhận thấy một bài đăng nói rằng nó không quan trọng nếu bạn gọi __stdcalltừ một __cdeclhoặc ngược lại. Nó làm.

Lý do: với __cdeclcác đối số được truyền cho các hàm được gọi sẽ bị loại bỏ khỏi ngăn xếp bởi hàm gọi, trong đó __stdcall, các đối số được loại bỏ khỏi ngăn xếp bởi hàm được gọi. Nếu bạn gọi một __cdeclhàm bằng a __stdcall, ngăn xếp hoàn toàn không được dọn sạch, vì vậy cuối cùng khi __cdeclsử dụng tham chiếu dựa trên xếp chồng cho các đối số hoặc địa chỉ trả về sẽ sử dụng dữ liệu cũ tại con trỏ ngăn xếp hiện tại. Nếu bạn gọi một __stdcallhàm từ a __cdecl, __stdcallhàm sẽ dọn sạch các đối số trên ngăn xếp và sau đó __cdeclhàm sẽ thực hiện lại, có thể loại bỏ các hàm gọi thông tin trả về.

Công ước Microsoft cho C cố gắng phá vỡ điều này bằng cách xáo trộn các tên. Một __cdeclchức năng có tiền tố với một dấu gạch dưới. Một __stdcalltiền tố hàm có dấu gạch dưới và được thêm vào với một dấu hiệu ở tên @ @ và số byte cần loại bỏ. Ví dụ như __cdeclf (x) được liên kết như _x, __stdcall f(int x)được liên kết như _f@4nơi sizeof(int)là 4 byte)

Nếu bạn quản lý để vượt qua trình liên kết, hãy tận hưởng mớ hỗn độn gỡ lỗi.

14 hữu ích 0 bình luận chia sẻ
3

Tôi muốn cải thiện câu trả lời của @ adf88. Tôi cảm thấy rằng mã giả cho STDCALL không phản ánh cách thức nó xảy ra trong thực tế. 'a', 'b' và 'c' không xuất hiện từ ngăn xếp trong thân hàm. Thay vào đó, chúng được bật theo rethướng dẫn ( ret 12sẽ được sử dụng trong trường hợp này) rằng trong một thao tác nhảy lại cho người gọi và đồng thời bật 'a', 'b' và 'c' từ ngăn xếp.

Đây là phiên bản của tôi được sửa theo sự hiểu biết của tôi:

STDCALL:

/* 1. calling STDCALL in pseudo-assembler (similar to what the compiler outputs) */
push on the stack a copy of 'z', then copy of 'y', then copy of 'x'
call
move contents of register A to 'i' variable

/* 2. STDCALL 'Function' body in pseaudo-assembler */ copy 'a' (from stack) to register A copy 'b' (from stack) to register B add A and B, store result in A copy 'c' (from stack) to register B add A and B, store result in A jump back to caller code and at the same time pop 'a', 'b' and 'c' off the stack (a, b and c are removed from the stack in this step, result in register A)

3 hữu ích 0 bình luận chia sẻ
2

Nó được chỉ định trong loại chức năng. Khi bạn có một con trỏ hàm, nó được coi là cdecl nếu không rõ ràng là stdcall. Điều này có nghĩa là nếu bạn nhận được một con trỏ stdcall và một con trỏ cdecl, bạn không thể trao đổi chúng. Hai loại chức năng có thể gọi cho nhau mà không có vấn đề, nó chỉ nhận được một loại khi bạn mong đợi loại khác. Về tốc độ, cả hai đều thực hiện các vai trò giống nhau, chỉ ở một nơi rất khác biệt, điều đó thực sự không liên quan.

2 hữu ích 0 bình luận chia sẻ
1

Người gọi và callee cần sử dụng cùng một quy ước tại điểm gọi - đó là cách duy nhất nó có thể hoạt động một cách đáng tin cậy. Cả người gọi và callee đều tuân theo một giao thức được xác định trước - ví dụ, người cần dọn dẹp ngăn xếp. Nếu các quy ước không khớp, chương trình của bạn sẽ chạy vào hành vi không xác định - có thể chỉ gặp sự cố một cách ngoạn mục.

Điều này chỉ được yêu cầu cho mỗi trang web gọi - chính mã gọi có thể là một chức năng với bất kỳ quy ước gọi nào.

Bạn không nên nhận thấy bất kỳ sự khác biệt thực sự trong hiệu suất giữa các quy ước. Nếu điều đó trở thành một vấn đề bạn thường cần thực hiện ít cuộc gọi hơn - ví dụ, thay đổi thuật toán.

1 hữu ích 0 bình luận chia sẻ
1

Những thứ đó là Trình biên dịch và Nền tảng cụ thể. Cả tiêu chuẩn C và C ++ đều không nói gì về việc gọi các quy ước ngoại trừ extern "C"trong C ++.

Làm thế nào để một người gọi biết nếu nó nên giải phóng ngăn xếp?

Người gọi biết quy ước gọi của hàm và xử lý cuộc gọi tương ứng.

Tại trang web cuộc gọi, người gọi có biết chức năng đang được gọi là cdecl hay chức năng stdcall không?

Vâng.

Làm thế nào nó hoạt động ?

Nó là một phần của khai báo hàm.

Làm thế nào để người gọi biết nếu nó nên giải phóng chồng hay không?

Người gọi biết các quy ước gọi và có thể hành động tương ứng.

Hay đó là trách nhiệm liên kết?

Không, quy ước gọi là một phần của khai báo hàm để trình biên dịch biết mọi thứ nó cần biết.

Nếu một hàm được khai báo là stdcall gọi một chức năng (trong đó có một quy ước gọi là cdecl), hoặc theo chiều ngược lại, điều này sẽ là không phù hợp?

Không. Tại sao lại như vậy?

Nói chung, chúng ta có thể nói rằng cuộc gọi nào sẽ nhanh hơn - cdecl hoặc stdcall?

Tôi không biết. Kiểm tra nó

1 hữu ích 0 bình luận chia sẻ
0

a) Khi một hàm cdecl được gọi bởi người gọi, làm thế nào để người gọi biết liệu nó có giải phóng được ngăn xếp không?

Công cụ cdeclsửa đổi là một phần của nguyên mẫu hàm (hoặc loại con trỏ hàm, v.v.) để người gọi nhận thông tin từ đó và hành động tương ứng.

b) Nếu một hàm được khai báo là stdcall gọi một hàm (có quy ước gọi là cdecl), hoặc ngược lại, điều này có phù hợp không?

Không, nó ổn.

c) Nói chung, chúng ta có thể nói rằng cuộc gọi nào sẽ nhanh hơn - cdecl hoặc stdcall?

Nói chung, tôi sẽ kiềm chế bất kỳ tuyên bố như vậy. Các vấn đề phân biệt ví dụ. khi bạn muốn sử dụng các hàm va_arg. Về lý thuyết, nó có thể stdcallnhanh hơn và tạo mã nhỏ hơn vì nó cho phép kết hợp popping các đối số với popping địa phương, nhưng OTOH với cdecl, bạn cũng có thể làm điều tương tự, nếu bạn thông minh.

Các quy ước gọi vốn nhằm mục đích nhanh hơn thường thực hiện một số lần đăng ký.

0 hữu ích 0 bình luận chia sẻ
1

Các quy ước gọi không liên quan gì đến các ngôn ngữ lập trình C / C ++ và khá cụ thể về cách trình biên dịch thực hiện ngôn ngữ đã cho. Nếu bạn luôn sử dụng cùng một trình biên dịch, bạn không bao giờ phải lo lắng về việc gọi các quy ước.

Tuy nhiên, đôi khi chúng tôi muốn mã nhị phân được biên dịch bởi các trình biên dịch khác nhau để hoạt động chính xác. Khi chúng ta làm như vậy, chúng ta cần xác định một cái gì đó gọi là Giao diện nhị phân ứng dụng (ABI). ABI định nghĩa cách trình biên dịch chuyển đổi nguồn C / C ++ thành mã máy. Điều này sẽ bao gồm các quy ước gọi, xáo trộn tên và bố trí bảng v. cdelc và stdcall là hai quy ước gọi khác nhau thường được sử dụng trên nền tảng x86.

Bằng cách đặt thông tin về quy ước gọi vào tiêu đề nguồn, trình biên dịch sẽ biết mã nào cần được tạo để tương tác chính xác với tệp thực thi đã cho.

1 hữu ích 0 bình luận chia sẻ
loading
Không tìm thấy câu trả lời bạn tìm kiếm? Duyệt qua các câu hỏi được gắn thẻ c++ stdcall cdecl , hoặc hỏi câu hỏi của bạn.

Có thể bạn quan tâm

loading