Theo như tiêu chuẩn C có liên quan, nếu bạn truyền một con trỏ hàm đến một con trỏ hàm thuộc kiểu khác và sau đó gọi nó, đó là hành vi không xác định . Xem Phụ lục J.2 (cung cấp thông tin):
Hành vi không được xác định trong các trường hợp sau:
- Con trỏ được sử dụng để gọi một hàm có kiểu không tương thích với kiểu trỏ đến (6.3.2.3).
Phần 6.3.2.3, đoạn 8 đọc:
Một con trỏ đến một chức năng của một kiểu có thể được chuyển đổi thành một con trỏ đến một chức năng của một kiểu khác và quay lại; kết quả sẽ được so sánh bằng con trỏ ban đầu. Nếu một con trỏ được chuyển đổi được sử dụng để gọi một hàm có kiểu không tương thích với kiểu trỏ tới, hành vi đó là không xác định.
Vì vậy, nói cách khác, bạn có thể ép kiểu con trỏ hàm đến một kiểu con trỏ hàm khác, ép kiểu lại và gọi nó, và mọi thứ sẽ hoạt động.
Định nghĩa về tương thích hơi phức tạp. Nó có thể được tìm thấy trong mục 6.7.5.3, đoạn 15:
Để hai kiểu chức năng tương thích, cả hai đều phải chỉ định kiểu trả về tương thích 127 .
Hơn nữa, danh sách kiểu tham số, nếu cả hai đều có mặt, sẽ thống nhất về số lượng tham số và cách sử dụng dấu chấm lửng; các tham số tương ứng phải có kiểu tương thích. Nếu một kiểu có danh sách kiểu tham số và kiểu khác được chỉ định bởi trình khai báo hàm không phải là một phần của định nghĩa hàm và chứa danh sách định danh trống, danh sách tham số sẽ không có dấu chấm lửng và kiểu của mỗi tham số sẽ tương thích với loại kết quả từ việc áp dụng các quảng cáo đối số mặc định. Nếu một kiểu có danh sách kiểu tham số và kiểu khác được chỉ định bởi định nghĩa hàm có chứa danh sách định danh (có thể trống), thì cả hai sẽ đồng ý về số lượng tham số, và kiểu của mỗi tham số nguyên mẫu phải tương thích với kiểu là kết quả của việc áp dụng các quảng cáo đối số mặc định cho kiểu của số nhận dạng tương ứng. (Trong việc xác định tính tương thích của kiểu và kiểu kết hợp, mỗi tham số được khai báo với kiểu hàm hoặc kiểu mảng được coi là có kiểu điều chỉnh và mỗi tham số được khai báo với kiểu đủ điều kiện được coi là có phiên bản không đủ điều kiện của kiểu đã khai báo.)
127) Nếu cả hai kiểu hàm đều là '' kiểu cũ '', thì các kiểu tham số sẽ không được so sánh.
Các quy tắc để xác định xem hai loại có tương thích hay không được mô tả trong phần 6.2.7 và tôi sẽ không trích dẫn chúng ở đây vì chúng khá dài, nhưng bạn có thể đọc chúng trên bản nháp của tiêu chuẩn C99 (PDF) .
Quy tắc liên quan ở đây nằm trong mục 6.7.5.1, đoạn 2:
Để hai loại con trỏ tương thích, cả hai đều phải đủ tiêu chuẩn giống nhau và cả hai đều phải là con trỏ tới các loại tương thích.
Do đó, vì a void*
không tương thích với a struct my_struct*
, một loại con trỏ hàm void (*)(void*)
không tương thích với một loại con trỏ hàm void (*)(struct my_struct*)
, do đó, việc đúc con trỏ hàm về mặt kỹ thuật là hành vi không xác định.
Tuy nhiên, trong thực tế, bạn có thể an toàn với việc truyền con trỏ hàm trong một số trường hợp. Trong quy ước gọi x86, các đối số được đẩy lên ngăn xếp và tất cả các con trỏ đều có cùng kích thước (4 byte trong x86 hoặc 8 byte trong x86_64). Việc gọi một con trỏ hàm dẫn đến việc đẩy các đối số trên ngăn xếp và thực hiện một bước nhảy gián tiếp đến mục tiêu con trỏ hàm và rõ ràng là không có khái niệm về các loại ở cấp mã máy.
Những điều bạn chắc chắn không thể làm:
- Truyền giữa các con trỏ hàm của các quy ước gọi khác nhau. Bạn sẽ làm xáo trộn ngăn xếp và tệ nhất là sụp đổ, tệ nhất là thành công một cách âm thầm với một lỗ hổng bảo mật khổng lồ. Trong lập trình Windows, bạn thường chuyển các con trỏ hàm xung quanh. Win32 hy vọng tất cả các chức năng gọi lại để sử dụng các
stdcall
quy ước gọi (mà các macro CALLBACK
, PASCAL
và WINAPI
tất cả các mở rộng sang). Nếu bạn truyền một con trỏ hàm sử dụng quy ước gọi C chuẩn ( cdecl
), thì lỗi sẽ dẫn đến.
- Trong C ++, ép kiểu giữa con trỏ hàm thành viên lớp và con trỏ hàm thông thường. Điều này thường làm cho người mới học C ++. Các hàm thành viên của lớp có một
this
tham số ẩn , và nếu bạn chuyển một hàm thành viên thành một hàm thông thường, sẽ không có this
đối tượng nào để sử dụng và một lần nữa, nhiều lỗi sẽ dẫn đến.
Một ý tưởng tồi khác đôi khi có thể hoạt động nhưng cũng là hành vi không xác định:
- Truyền giữa con trỏ hàm và con trỏ thông thường (ví dụ: ép kiểu a
void (*)(void)
thành a void*
). Con trỏ hàm không nhất thiết phải có cùng kích thước với con trỏ thông thường, vì trên một số kiến trúc, chúng có thể chứa thêm thông tin ngữ cảnh. Điều này có thể sẽ hoạt động tốt trên x86, nhưng hãy nhớ rằng đó là hành vi không xác định.