Hãy xem xét signal()
hàm từ tiêu chuẩn C:
extern void (*signal(int, void(*)(int)))(int);
Rõ ràng hoàn toàn rõ ràng - đó là một hàm lấy hai đối số, một số nguyên và một con trỏ tới một hàm lấy một số nguyên làm đối số và không trả về gì, và nó ( signal()
) trả về một con trỏ tới một hàm lấy một số nguyên làm đối số và trả về không có gì.
Nếu bạn viết:
typedef void (*SignalHandler)(int signum);
sau đó bạn có thể khai báo signal()
là:
extern SignalHandler signal(int signum, SignalHandler handler);
Điều này có nghĩa là điều tương tự, nhưng thường được coi là hơi dễ đọc hơn. Rõ ràng hơn là hàm lấy một int
và a SignalHandler
và trả về a SignalHandler
.
Nó cần một chút để làm quen, mặc dù. Tuy nhiên, một điều bạn không thể làm là viết hàm xử lý tín hiệu bằng cách sử dụng SignalHandler
typedef
định nghĩa hàm.
Tôi vẫn thuộc trường phái cũ thích gọi một con trỏ hàm là:
(*functionpointer)(arg1, arg2, ...);
Cú pháp hiện đại chỉ sử dụng:
functionpointer(arg1, arg2, ...);
Tôi có thể thấy lý do tại sao điều đó hoạt động - Tôi chỉ muốn biết rằng tôi cần tìm nơi biến được khởi tạo chứ không phải cho một hàm được gọi functionpointer
.
Sam nhận xét:
Tôi đã thấy lời giải thích này trước đây. Và sau đó, như trường hợp hiện tại, tôi nghĩ điều tôi không nhận được là sự kết nối giữa hai tuyên bố:
extern void (*signal(int, void()(int)))(int); /*and*/
typedef void (*SignalHandler)(int signum);
extern SignalHandler signal(int signum, SignalHandler handler);
Hoặc, điều tôi muốn hỏi là, khái niệm cơ bản mà người ta có thể sử dụng để đưa ra phiên bản thứ hai bạn có là gì? Cơ sở kết nối "SignalHandler" và typedef đầu tiên là gì? Tôi nghĩ những gì cần được giải thích ở đây là những gì typedef đang thực sự làm ở đây.
Hãy thử lại lần nữa. Đầu tiên trong số này được nâng thẳng từ tiêu chuẩn C - tôi đã thử lại và kiểm tra xem tôi có đúng dấu ngoặc đơn không (cho đến khi tôi sửa nó - đó là một cookie khó nhớ).
Trước hết, hãy nhớ rằng typedef
giới thiệu một bí danh cho một loại. Vì vậy, bí danh là SignalHandler
, và loại của nó là:
một con trỏ tới một hàm lấy một số nguyên làm đối số và không trả về gì.
Phần 'trả về không có gì' được đánh vần void
; đối số là một số nguyên là (tôi tin tưởng) tự giải thích. Ký hiệu sau chỉ đơn giản là (hoặc không) cách C đánh vần con trỏ tới hàm lấy các đối số như đã chỉ định và trả về kiểu đã cho:
type (*function)(argtypes);
Sau khi tạo kiểu xử lý tín hiệu, tôi có thể sử dụng nó để khai báo các biến và cứ thế. Ví dụ:
static void alarm_catcher(int signum)
{
fprintf(stderr, "%s() called (%d)\n", __func__, signum);
}
static void signal_catcher(int signum)
{
fprintf(stderr, "%s() called (%d) - exiting\n", __func__, signum);
exit(1);
}
static struct Handlers
{
int signum;
SignalHandler handler;
} handler[] =
{
{ SIGALRM, alarm_catcher },
{ SIGINT, signal_catcher },
{ SIGQUIT, signal_catcher },
};
int main(void)
{
size_t num_handlers = sizeof(handler) / sizeof(handler[0]);
size_t i;
for (i = 0; i < num_handlers; i++)
{
SignalHandler old_handler = signal(handler[i].signum, SIG_IGN);
if (old_handler != SIG_IGN)
old_handler = signal(handler[i].signum, handler[i].handler);
assert(old_handler == SIG_IGN);
}
...continue with ordinary processing...
return(EXIT_SUCCESS);
}
Xin lưu ý Làm thế nào để tránh sử dụng printf()
trong một bộ xử lý tín hiệu?
Vì vậy, những gì chúng ta đã làm ở đây - ngoài việc bỏ qua 4 tiêu đề cần thiết để làm cho mã được biên dịch sạch sẽ?
Hai hàm đầu tiên là các hàm lấy một số nguyên duy nhất và không trả về gì. Một trong số họ thực sự không quay trở lại nhờ vào exit(1);
nhưng người kia sẽ quay lại sau khi in một tin nhắn. Xin lưu ý rằng tiêu chuẩn C không cho phép bạn thực hiện nhiều thao tác xử lý tín hiệu; POSIX hào phóng hơn một chút trong những gì được phép, nhưng chính thức không xử phạt cuộc gọi fprintf()
. Tôi cũng in ra số tín hiệu đã nhận được. Trong alarm_handler()
hàm, giá trị sẽ luôn luôn SIGALRM
là tín hiệu duy nhất mà nó là một hàm xử lý, nhưng signal_handler()
có thể lấy SIGINT
hoặc SIGQUIT
làm số tín hiệu vì cùng một hàm được sử dụng cho cả hai.
Sau đó, tôi tạo một mảng các cấu trúc, trong đó mỗi phần tử xác định một số tín hiệu và trình xử lý sẽ được cài đặt cho tín hiệu đó. Tôi đã chọn lo lắng về 3 tín hiệu; Tôi thường lo lắng SIGHUP
, SIGPIPE
và SIGTERM
cũng như về việc liệu chúng có được xác định ( #ifdef
biên dịch có điều kiện) không, nhưng điều đó chỉ làm phức tạp mọi thứ. Tôi cũng có thể sử dụng POSIX sigaction()
thay vì signal()
, nhưng đó là một vấn đề khác; hãy gắn bó với những gì chúng ta bắt đầu.
Các main()
lặp chức năng trong danh sách các bộ xử lý được cài đặt. Đối với mỗi trình xử lý, trước tiên, nó gọi signal()
để tìm hiểu xem liệu quy trình hiện đang bỏ qua tín hiệu hay không, và trong khi thực hiện, cài đặt SIG_IGN
như trình xử lý, đảm bảo tín hiệu được bỏ qua. Nếu tín hiệu trước đây không bị bỏ qua, thì nó sẽ gọi signal()
lại, lần này để cài đặt trình xử lý tín hiệu ưa thích. (Giá trị còn lại là có lẽ SIG_DFL
, xử lý tín hiệu mặc định cho các tín hiệu.) Bởi vì các cuộc gọi đầu tiên 'tín hiệu ()' thiết lập các handler để SIG_IGN
và signal()
trả về xử lý lỗi trước, giá trị của old
sau khi if
tuyên bố phải SIG_IGN
- vì thế khẳng định. (Vâng, nó có thể làSIG_ERR
nếu có gì đó không ổn - nhưng sau đó tôi sẽ tìm hiểu về điều đó từ việc bắn khẳng định.)
Chương trình sau đó thực hiện công cụ của nó và thoát ra bình thường.
Lưu ý rằng tên của một chức năng có thể được coi là một con trỏ đến một chức năng của loại thích hợp. Khi bạn không áp dụng dấu ngoặc đơn gọi hàm - ví dụ như trong bộ khởi tạo - tên hàm sẽ trở thành một con trỏ hàm. Đây cũng là lý do tại sao việc gọi các hàm thông qua pointertofunction(arg1, arg2)
ký hiệu là hợp lý ; Khi bạn nhìn thấy alarm_handler(1)
, bạn có thể coi đó alarm_handler
là một con trỏ tới hàm và do đó alarm_handler(1)
là một lời gọi của hàm thông qua một con trỏ hàm.
Vì vậy, cho đến nay, tôi đã chỉ ra rằng một SignalHandler
biến tương đối dễ sử dụng, miễn là bạn có một số loại giá trị phù hợp để gán cho nó - đó là những gì hai hàm xử lý tín hiệu cung cấp.
Bây giờ chúng ta quay trở lại câu hỏi - làm thế nào để hai khai báo signal()
liên quan đến nhau.
Hãy xem lại tuyên bố thứ hai:
extern SignalHandler signal(int signum, SignalHandler handler);
Nếu chúng ta thay đổi tên hàm và kiểu như thế này:
extern double function(int num1, double num2);
bạn sẽ không gặp vấn đề gì khi diễn giải điều này như là một hàm lấy int
và double
làm đối số và trả về một double
giá trị (bạn có thể không nên lo lắng nếu điều đó có vấn đề - nhưng có lẽ bạn nên thận trọng khi đặt câu hỏi quá khó như cái này nếu nó là một vấn đề)
Bây giờ, thay vì là một double
, signal()
hàm lấy một SignalHandler
đối số thứ hai và nó trả về một kết quả như là kết quả của nó.
Các cơ chế mà cũng có thể được coi là:
extern void (*signal(int signum, void(*handler)(int signum)))(int signum);
rất khó để giải thích - vì vậy tôi có thể sẽ làm hỏng nó lên. Lần này tôi đã đưa ra các tên tham số - mặc dù tên không quan trọng.
Nói chung, trong C, cơ chế khai báo là như vậy nếu bạn viết:
type var;
sau đó khi bạn viết var
nó đại diện cho một giá trị của cái đã cho type
. Ví dụ:
int i; // i is an int
int *ip; // *ip is an int, so ip is a pointer to an integer
int abs(int val); // abs(-1) is an int, so abs is a (pointer to a)
// function returning an int and taking an int argument
Trong tiêu chuẩn, typedef
được coi là một lớp lưu trữ trong ngữ pháp, thay vì thích static
và extern
là các lớp lưu trữ.
typedef void (*SignalHandler)(int signum);
có nghĩa là khi bạn thấy một biến loại SignalHandler
(nói alarm_handler) được gọi là:
(*alarm_handler)(-1);
kết quả có type void
- không có kết quả. Và (*alarm_handler)(-1);
là một lời mời alarm_handler()
với tranh luận -1
.
Vì vậy, nếu chúng tôi tuyên bố:
extern SignalHandler alt_signal(void);
nó có nghĩa là:
(*alt_signal)();
đại diện cho một giá trị void. Và do đó:
extern void (*alt_signal(void))(int signum);
là tương đương Bây giờ, signal()
phức tạp hơn vì nó không chỉ trả về a SignalHandler
, mà còn chấp nhận cả int và a SignalHandler
dưới dạng đối số:
extern void (*signal(int signum, SignalHandler handler))(int signum);
extern void (*signal(int signum, void (*handler)(int signum)))(int signum);
Nếu điều đó vẫn làm bạn bối rối, tôi không biết làm cách nào để giúp đỡ - nó vẫn ở một số mức độ bí ẩn đối với tôi, nhưng tôi đã quen với cách nó hoạt động và do đó có thể nói với bạn rằng nếu bạn gắn bó với nó thêm 25 năm nữa hoặc như vậy, nó sẽ trở thành bản chất thứ hai đối với bạn (và thậm chí có thể nhanh hơn một chút nếu bạn khéo léo).