[C] Function Pointer (Con trỏ hàm) - P2

Ngô Văn Tuân

Gà con
Staff member
Bài này sẽ trình bày một số ví dụ ứng dụng con trỏ hàm:

1. Supervisor call interface
C:
#include <stdio.h>
#include <stdint.h>

/* Library */
int add(int a, int b) {
    return a + b;
}
/* End of library */

int main()
{

    int svc;
    svc = (int)(&add);

    int sum = ((int(*)(int,int))svc)(100, 200);

    printf("sum=%d\n", sum);
    return 0;
}
sum=300
Lưu ý: Chương trình phải build x86 (32-bit)
Giải thích:
  • Ta khai báo biến svc kiểu int, do đó svc có chiều dài 4 bytes, đủ để lưu địa chỉ của một hàm
  • svc = (int)(&add); Lấy địa chỉ hàm add, ép sang kiểu int và lưu vào biến svc
  • Lúc này, svc là một biến bình thường, không phải biến con trỏ nhưng lại lưu địa chỉ của hàm
  • int sum = ((int(*)(int,int))svc)(100, 200); Ép kiểu svc từ int qua (int(*)(int,int) và dùng nó để gọi hàm add
Nhận xét 1:
  • Thoạt nhìn ta thấy việc ép kiểu từ (int(*)(int,int) qua int rồi lại từ int qua (int(*)(int,int) có vẻ khá vô nghĩa, nhưng thực ra là nó vô nghĩa thật ;)
  • Mục đích của ví dụ trên là để chứng tỏ cho các bạn thấy rằng ta có thể gọi một hàm nếu biết địa chỉ của hàm đó. Rõ ràng svc là một số nguyên 4 bytes và ta có thể gán rất nhiều giá trị khác nhau cho biến này. Nhưng bằng cách nào đó, ta biết được địa chỉ của hàm add và gán nó vào biến svc. Khi đó ta có thể gọi hàm add thông qua biến svc.
Nhận xét 2: Đối với VĐK, trong trường ta luôn sử dụng một thư viện lớn để build code thì việc nạp thư viện đó xuống VĐK thường xuyên sẽ khá tốn thời gian và giảm tuổi thọ bộ nhớ (đọc ghi nhiều). Giải pháp là nạp trước các hàm trong thư viện vào các vị trí cố định trong bộ nhớ. Trong chương trình chính, chúng ta sẽ sử dụng các địa chỉ biết trước đó để gọi hàm. Cách gọi hàm như vậy gọi là Supersior Call. Bằng cách này, chúng ta chỉ cần nạp thư viện một lần. Các lần tiếp theo chỉ cần nạp chương trình chính của chúng ta.

2. Mô phỏng quá trình pass sự kiện
C:
#include <stdio.h>
#include <stdint.h>
#include <ctype.h>

/* Library */
typedef void (*on_char_input_handler_t)(char);

on_char_input_handler_t on_char_input_handler;

void set_on_char_input_handler(on_char_input_handler_t ptr) {
    on_char_input_handler = ptr;
}

void run() {
    while (1) {
        char c = getchar();
        on_char_input_handler(c);
    }
}
/* End of library */

void print_char(char chr) {
    printf("%c", toupper(chr));
}

int main()
{
    set_on_char_input_handler(&print_char);
    run();
    return 0;
}
a
A
b
B
Giải thích:
  • Chương trình trên yêu cầu nhập vào ký tự và in ra màn hình ký tự in hoa tương ứng.
  • Phần trong khung /* Library *//* End of library */ là phần thư viện và có thể được viết ở một file C khác.
  • Trong ví dụ trên, ta đặc biệt chú ý vào hàm set_on_char_input_handler(&print_char);. Hàm này chỉ CPU hãy chạy hàm void print_char(char chr) khi có sự kiện: có một ký tự được nhập vào.
  • Hàm void print_char(char chr) được viết trong chương trình chính và ta hoàn toàn có thể thay thế hàm này bằng các hàm khác miễn là nó có kiểu void (*)(char). Đây là nguyên lý để chỉ CPU thực hiện một công việc nào đó khi có sự kiện kiện xảy ra.
3. Chạy hàm với tần số cho trước
Lưu ý:
  • Phần trong khung /* Library *//* End of library */ là phần thư viện và có thể được viết ở một file C khác.
  • Mục tiêu chương trình là chạy 3 hàm với yêu cầu sau đây:

STTHàmYêu cầu
1​
void two_s_repeat_thread(char*)Cứ 2 giây chạy một lần
2​
void five_s_singleshot_thread(char*)Chạy sau 5s kể từ khi bắt đầu chương trình, chỉ chạy một lần
3​
void ten_s_singleshot_thread(char* ctx)Chạy sau 10s kể từ khi bắt đầu. Dừng quá trình chạy lặp sau mỗi 2s của hàm 1
C:
#include <stdio.h>
#include <stdint.h>
#include <ctype.h>
#include <time.h>

/* Library */
#define MAX_THREAD 10

typedef void (*thread_func_t)(char*);
typedef int thread_id_t;                 
typedef enum {
    THREAD_REPEAT,          // Function run periodically
    THREAD_SINGLESHOT       // Function only run one time
}thread_type_t;

typedef struct {
    thread_func_t thread_func;  // Pointer to function
    int period;                 // Period of function
    int cnt;                    // Internal counter
    thread_type_t type;         // Type of thread
    char* ctx;                  // Give this to function when run it
}thread_t;

thread_t m_thread[MAX_THREAD];

thread_id_t register_thread(thread_func_t thread_func,int period_ms, thread_type_t type, char* context) {
    for (int i = 0; i < MAX_THREAD; i++) {
        if (m_thread[i].thread_func == NULL) // Check if is there any empty slot
        {
            m_thread[i].thread_func = thread_func;
            m_thread[i].period = period_ms;
            m_thread[i].cnt = period_ms;
            m_thread[i].type = type;
            m_thread[i].ctx = context;
            return i;
        }
    }
    return -1; // if there is no slot remain, return invalid id
}

bool unregister_thread(thread_id_t proc_id) {
    if (m_thread[proc_id].thread_func != NULL) {
        m_thread[proc_id].thread_func = NULL;
        return true;
    }
    else {
        return false;
    }
}

void one_ms_timer_interrupt() {
    for (int i = 0; i < MAX_THREAD; i++) {
        if (m_thread[i].thread_func != NULL) {
            m_thread[i].cnt--;                                  // Reduce internal counter
            if (m_thread[i].cnt == 0) {                         // Check if timeout
                m_thread[i].thread_func(m_thread[i].ctx);       // Run fucntion with context   
                if (m_thread[i].type == THREAD_REPEAT) {        // Check thread type
                    m_thread[i].cnt = m_thread[i].period;       // Reset counter
                }
                else if(m_thread[i].type == THREAD_SINGLESHOT)
                {
                    m_thread[i].thread_func = NULL;             // Unregister thread
                }
            }
        }
    }
}
/* End of library */

void two_s_repeat_thread(char*) {
    printf("This is 2s repeat thread\n");
}

void five_s_singleshot_thread(char*) {
    printf("This is 5s repeat thread\n");
}

void ten_s_singleshot_thread(char* ctx) {
    bool ok = unregister_thread((thread_id_t)*ctx);
    if (ok) {
        printf("Succeed to unregister thread with id %d \n", (thread_id_t)*ctx);
    }
    else {
        printf("Failed to unregister thread with id %d \n", (thread_id_t)*ctx);
    }
}

int main()
{

    thread_id_t id0 = register_thread(&two_s_repeat_thread, 2000, THREAD_REPEAT, NULL);
    thread_id_t id1 = register_thread(&five_s_singleshot_thread, 5000, THREAD_SINGLESHOT, NULL);
    thread_id_t id2 = register_thread(&ten_s_singleshot_thread, 10000, THREAD_SINGLESHOT, (char*)&id0);

    while (1) {
        one_ms_timer_interrupt();
      
        // Delay 1ms
        clock_t start_time = clock();
        while (clock() < start_time + 1);
    }
    return 0;
}
This is 2s repeat thread
This is 2s repeat thread
This is 5s repeat thread
This is 2s repeat thread
This is 2s repeat thread
This is 2s repeat thread
Succeed to unregister thread with id 0

Bài trước: [C] Function Pointer (Con trỏ hàm) - P1
Bài tiếp:
 
Last edited:
Top