Tôi muốn tạo một con trỏ hàm tới một hàm sẽ xử lý một tập hợp con các trường hợp cho một hàm nhận danh sách tham số biến. Trường hợp sử dụng đang truyền một hàm mất ...
đến một hàm có danh sách thông số cụ thể, do đó bạn có thể xử lý các thông số biến mà không phải xử lý va_list
và bạn bè.Trong C, có bao giờ an toàn khi đúc con trỏ hàm variadic đến một con trỏ hàm với các đối số hữu hạn không?
Trong mã ví dụ sau, tôi đang truyền hàm có tham số biến cho hàm có danh sách tham số được mã hóa cứng (và ngược lại). Điều này làm việc (hoặc xảy ra để làm việc), nhưng tôi không biết nếu là một sự trùng hợp do quy ước gọi đang sử dụng. (Tôi đã thử nó trên hai nền tảng dựa trên x86_64 khác nhau.)
#include <stdio.h>
#include <stdarg.h>
void function1(char* s, ...)
{
va_list ap;
int tmp;
va_start(ap, s);
tmp = va_arg(ap, int);
printf(s, tmp);
va_end(ap);
}
void function2(char* s, int d)
{
printf(s, d);
}
typedef void (*functionX_t)(char*, int);
typedef void (*functionY_t)(char*, ...);
int main(int argc, char* argv[])
{
int x = 42;
/* swap! */
functionX_t functionX = (functionX_t) function1;
functionY_t functionY = (functionY_t) function2;
function1("%d\n", x);
function2("%d\n", x);
functionX("%d\n", x);
functionY("%d\n", x);
return 0;
}
Hành vi không xác định này? Nếu có, bất cứ ai có thể đưa ra một ví dụ về một nền tảng mà điều này sẽ không làm việc, hoặc một cách để tinh chỉnh ví dụ của tôi theo cách nó sẽ thất bại, cho một trường hợp sử dụng phức tạp hơn?
Sửa: Để giải quyết những ngụ ý rằng đoạn mã này sẽ phá vỡ với lý lẽ phức tạp hơn, tôi mở rộng ví dụ của tôi:
#include <stdio.h>
#include <stdarg.h>
struct crazy
{
float f;
double lf;
int d;
unsigned int ua[2];
char* s;
};
void function1(char* s, ...)
{
va_list ap;
struct crazy c;
va_start(ap, s);
c = va_arg(ap, struct crazy);
printf(s, c.s, c.f, c.lf, c.d, c.ua[0], c.ua[1]);
va_end(ap);
}
void function2(char* s, struct crazy c)
{
printf(s, c.s, c.f, c.lf, c.d, c.ua[0], c.ua[1]);
}
typedef void (*functionX_t)(char*, struct crazy);
typedef void (*functionY_t)(char*, ...);
int main(int argc, char* argv[])
{
struct crazy c =
{
.f = 3.14,
.lf = 3.1415,
.d = -42,
.ua = { 0, 42 },
.s = "this is crazy"
};
/* swap! */
functionX_t functionX = (functionX_t) function1;
functionY_t functionY = (functionY_t) function2;
function1("%s %f %lf %d %u %u\n", c);
function2("%s %f %lf %d %u %u\n", c);
functionX("%s %f %lf %d %u %u\n", c);
functionY("%s %f %lf %d %u %u\n", c);
return 0;
}
Nó vẫn hoạt động. Bất cứ ai có thể chỉ ra một ví dụ cụ thể khi điều này sẽ thất bại?
$ gcc -Wall -g -o varargs -O9 varargs.c
$ ./varargs
this is crazy 3.140000 3.141500 -42 0 42
this is crazy 3.140000 3.141500 -42 0 42
this is crazy 3.140000 3.141500 -42 0 42
this is crazy 3.140000 3.141500 -42 0 42
"là hành vi không xác định điều này?" Vâng. Gọi một hàm thông qua một con trỏ hàm không tương thích luôn mang lại hành vi không xác định. –
Dường như với tôi (ít nhất là bằng ví dụ) rằng các thông số đẩy vào ngăn xếp được thực hiện một cách nhất quán bất kể nếu một cuộc gọi variadic được sử dụng hay không. Vì vậy, trong khi nó thực sự có thể là "không xác định" cho mỗi spec (hoặc có thể chỉ không được đề cập?), Là hành vi bao giờ thực sự không phù hợp? – mpontillo
@Mike: Các tham số Non-variadic được truyền qua thanh ghi CPU bất cứ khi nào có thể (ít nhất là trên x86). Họ không được đẩy vào ngăn xếp cả. Đây là một lý do tại sao nó không hoạt động. – AnT