04/11/2018, 17:15

[C Extended] Gọi Hàm Với Struct

Unsafe function Trong C , khi ta khai báo một hàm, sau đó sử dụng hàm đã khai báo trước đó thì hàm đó được gọi là safe , mọi thứ bạn đã khai báo và làm theo đều ăn khớp với nhau. int add ( int a , int b ) { return a + b ; } add ( 4 , 5 ) ; // safe Vậy làm ...

Unsafe function

Trong C, khi ta khai báo một hàm, sau đó sử dụng hàm đã khai báo trước đó thì hàm đó được gọi là safe, mọi thứ bạn đã khai báo và làm theo đều ăn khớp với nhau.

int add(int a, int b) {
    return a + b;
}
add(4, 5); // safe

Vậy làm sao để có unsafe?

int (*add2)() = add;
int val = add2(4, 5); // unsafe

Đoạn code trên, add2 sẽ là mang giá trị của add, tuy nhiên nó không được khai báo rõ là sẽ có những tham số gì bên trong, và khi gọi nó, bạn có thể thêm bất cứ thứ gì vào để làm tham số, tất nhiên nó sẽ là unsafe.

Nếu như phần tham số trong cặp ngoặc tròn bạn để là (int, int) thì sẽ trở thành safe nhé.

Vậy unsafe function có lợi gì?

  • Bạn có thể vượt qua giới hạn của static typically, có thể pass bất thì tham số nào vào hàm mà không cần quan tâm có đúng hay không (unsafe đúng nghĩa). Phương pháp này thường được sử dụng trong các ngôn ngữ dynamic typically hoặc scripting, ta có pointer của hàm và có thể gọi nó theo bất kỳ cách nào.
  • Tất nhiên cũng có nhiều cái hại như null pointer, crash do sai tham số...

Dùng struct thay cho tham số của hàm

Có chắc phương pháp này thực dụng? Test thử code sau:

void abc(int a, int b, unsigned c, unsigned d) {
    printf("addr: %d, %d, %d, %d
", &a, &b, &c, &d);
}
abc(1, 4, 7, 16);
addr: 10000, 10004, 10012, 10016

Kết quả cho thấy pointer của từng tham số liền kề nhau theo offset (size of type) của chúng. Nhưng đối với double thì offset là +0x6, nhưng nó không ảnh hưởng gì.

Như mình đã nói ở trên thì cách này chỉ dùng được cho unsafe function, bởi vì hàm đã khai báo chính xác thì không thể nào bỏ bớt tham số khi gọi được.

Thử nghiệm với hàm add trong phần đầu:

int (*add2)() = add;

struct { int a; int b; } args = { 4, 5 };
add2(args); // -> 9

Kết quả ngoài mong đợi, 4 + 5 == 9. Nhưng hàm add thì quá đơn giản, ta thừ hàm khác xem.

void test(int a, double b, int c) {
    printf("result: %d, %lf, %d
", a, b, c);
}
struct { int a; double b; int c; } args = { 2, 5.67, 15 };

((void(*)())test)(args); // unsafe
test(2, 5.67, 15); // safe, đối chiếu
result: 2, 80298907454632023000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.000000, 1075228180
result: 2, 5.6700000, 15

Output thật đẹp phải không nào, giá trị param 2 và 3 sẽ sai, đơn giản vì double ở vị trí thứ hai trong struct sẽ làm thừa ra đến 4 byte như mình đã nói trong post Dynamic Struct.

Vậy dùng struct động có được không?

void* args = _alloca(sizeof(int) + 
    sizeof(double) + sizeof(unsigned)
); 
// dùng calloc cũng được, nhưng trên MSVC sẽ lỗi, nó bắt buộc cho vào stack

*(int*)((char*)args + 0) = 2;
*(double*)((char*)args + 4) = 5.67;
*(unsigned*)((char*)args + 12) = 15;

Đệt! @ Đến đây chỉ có pointer thì pass vào kiểu gì, struct đâu?

typedef struct {
    int _0; long long _1;
    void* _2; char* _3;
    float _4; double _5;
} not_union_t;

((void(*)())test)(*(not_union_t*)args);
result: 2, 5.6700000, 15

Bạn thấy như thế nào? Thật kì diệu phải không, struct not_union_t được mình phỏng theo union VARIANT sử dụng cho Win32 COM/Object, mục đích của việc cast là bạn sẽ pass vào đó một struct anonymous, compiler sẽ hiểu theo struct đó và đưa vào các giá trị theo pointer, không theo thứ tự các element trong struct not_union_t.

Nên nhớ, khi compile thì nên disable all warning nhé, đối với MSVC thì phải tắt SDL Check đi /sdl-.

0