พิจารณาsignal()
ฟังก์ชั่นจากมาตรฐาน C:
extern void (*signal(int, void(*)(int)))(int);
ชัดเจนอย่างสมบูรณ์แบบ - เป็นฟังก์ชั่นที่รับสองอาร์กิวเมนต์จำนวนเต็มและตัวชี้ไปยังฟังก์ชันที่ใช้จำนวนเต็มเป็นอาร์กิวเมนต์และส่งคืนค่าใด ๆ และมันsignal()
ส่งคืนตัวชี้ไปยังฟังก์ชันที่ใช้จำนวนเต็มเป็นอาร์กิวเมนต์และส่งกลับ ไม่มีอะไร
ถ้าคุณเขียน:
typedef void (*SignalHandler)(int signum);
จากนั้นคุณสามารถประกาศsignal()
เป็น:
extern SignalHandler signal(int signum, SignalHandler handler);
นี่หมายถึงสิ่งเดียวกัน แต่โดยทั่วไปถือว่าอ่านง่ายกว่า มันเป็นที่ชัดเจนว่าการทำงานจะใช้เวลาint
และและผลตอบแทนSignalHandler
SignalHandler
มันใช้เวลาเล็กน้อยในการทำความคุ้นเคย สิ่งหนึ่งที่คุณทำไม่ได้แม้ว่าจะเขียนฟังก์ชั่นตัวจัดการสัญญาณโดยใช้SignalHandler
typedef
ในการกำหนดฟังก์ชั่น
ฉันยังอยู่ในโรงเรียนเก่าที่ชอบเรียกใช้ตัวชี้ฟังก์ชั่นเป็น:
(*functionpointer)(arg1, arg2, ...);
ไวยากรณ์สมัยใหม่ใช้เพียง:
functionpointer(arg1, arg2, ...);
ฉันเห็นได้ว่าทำไมมันถึงได้ผล - ฉันแค่อยากรู้ว่าฉันต้องมองหาว่าตัวแปรนั้นถูกเริ่มต้นแทนที่จะฟังก์ชั่นที่เรียกว่า functionpointer
ฉันเพียงแค่ต้องการที่จะรู้ว่าฉันต้องมองหาที่ตัวแปรจะเริ่มต้นได้มากกว่าฟังก์ชั่นที่เรียกว่า
แซมแสดงความคิดเห็น:
ฉันเคยเห็นคำอธิบายนี้มาก่อน จากนั้นเป็นกรณีนี้ฉันคิดว่าสิ่งที่ฉันไม่ได้รับคือการเชื่อมต่อระหว่างสองข้อความ:
extern void (*signal(int, void()(int)))(int); /*and*/
typedef void (*SignalHandler)(int signum);
extern SignalHandler signal(int signum, SignalHandler handler);
หรือสิ่งที่ฉันต้องการถามคืออะไรแนวคิดพื้นฐานที่หนึ่งสามารถใช้มากับรุ่นที่สองที่คุณมี พื้นฐานที่เชื่อมต่อ "SignalHandler" และตัวพิมพ์แรกคืออะไร ฉันคิดว่าสิ่งที่ต้องอธิบายที่นี่คือสิ่งที่ typedef กำลังทำอยู่ที่นี่
ลองอีกครั้ง ครั้งแรกของเหล่านี้ยกตรงจากมาตรฐาน C - ฉันพิมพ์ใหม่อีกครั้งและตรวจสอบว่าฉันมีวงเล็บขวา (ไม่จนกว่าฉันจะแก้ไขมัน - มันเป็นคุกกี้ที่ยากที่จะจำ)
ก่อนอื่นให้จำไว้ว่าtypedef
แนะนำชื่อแทนสำหรับประเภท ดังนั้นนามแฝงคือSignalHandler
และประเภทของมันคือ:
ตัวชี้ไปยังฟังก์ชันที่ใช้จำนวนเต็มเป็นอาร์กิวเมนต์และส่งกลับค่าอะไร
ส่วน 'ส่งคืนอะไร' สะกดถูกvoid
; อาร์กิวเมนต์ที่เป็นจำนวนเต็มคือ (ฉันเชื่อ) อธิบายตนเอง สัญกรณ์ต่อไปนี้เป็นเพียงแค่ (หรือไม่) วิธีที่ C คาถาชี้ไปที่ฟังก์ชั่นการรับข้อโต้แย้งตามที่ระบุและส่งกลับประเภทที่กำหนด:
type (*function)(argtypes);
หลังจากสร้างประเภทตัวจัดการสัญญาณฉันสามารถใช้มันเพื่อประกาศตัวแปรและอื่น ๆ ตัวอย่างเช่น:
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);
}
โปรดทราบวิธีการหลีกเลี่ยงการใช้printf()
ในตัวจัดการสัญญาณ?
ดังนั้นเราได้ทำอะไรที่นี่ - นอกจากละเว้นส่วนหัวมาตรฐาน 4 ที่จะต้องทำให้รหัสรวบรวมได้อย่างหมดจด?
สองฟังก์ชั่นแรกคือฟังก์ชั่นที่ใช้จำนวนเต็มเดียวและไม่คืนค่าอะไรเลย อันที่จริงคนหนึ่งไม่ได้กลับมาขอบคุณอีกต่อไปexit(1);
แต่อีกคนหนึ่งกลับมาหลังจากพิมพ์ข้อความ พึงระวังว่ามาตรฐาน C ไม่อนุญาตให้คุณทำในตัวจัดการสัญญาณมากนัก POSIXเป็นบิตใจกว้างมากขึ้นในสิ่งที่จะได้รับอนุญาตอย่างเป็นทางการ fprintf()
แต่ไม่ได้โทรอนุมัติ ฉันยังพิมพ์หมายเลขสัญญาณที่ได้รับ ในalarm_handler()
ฟังก์ชั่นค่าจะเป็นSIGALRM
เช่นเดียวกับที่เป็นสัญญาณเดียวที่เป็นตัวจัดการ แต่signal_handler()
อาจได้รับSIGINT
หรือSIGQUIT
เป็นหมายเลขสัญญาณเพราะใช้ฟังก์ชั่นเดียวกันสำหรับทั้งคู่
จากนั้นฉันสร้างอาร์เรย์ของโครงสร้างที่แต่ละองค์ประกอบระบุหมายเลขสัญญาณและตัวจัดการที่จะติดตั้งสำหรับสัญญาณนั้น ฉันเลือกที่จะกังวลเกี่ยวกับ 3 สัญญาณ; ผมมักจะกังวลเกี่ยวกับSIGHUP
, SIGPIPE
และSIGTERM
เกินไปและเกี่ยวกับว่าพวกเขาจะมีการกำหนด ( #ifdef
รวบรวมเงื่อนไข) แต่ที่มีความซับซ้อนเพียงแค่สิ่งที่ ฉันอาจใช้ POSIX sigaction()
แทนsignal()
แต่นั่นก็เป็นอีกปัญหาหนึ่ง มายึดติดกับสิ่งที่เราเริ่มต้นด้วย
main()
iterates ฟังก์ชั่นมากกว่ารายการไสที่จะติดตั้ง สำหรับตัวจัดการแต่ละตัวมันจะเรียกก่อนsignal()
เพื่อดูว่ากระบวนการกำลังเพิกเฉยสัญญาณหรือไม่และในขณะนั้นให้ติดตั้งSIG_IGN
เป็นตัวจัดการซึ่งทำให้มั่นใจได้ว่าสัญญาณจะถูกเพิกเฉย หากไม่เคยถูกละเลยสัญญาณก่อนหน้านี้สัญญาณจะถูกเรียกsignal()
อีกครั้งคราวนี้เพื่อติดตั้งตัวจัดการสัญญาณที่ต้องการ (ค่าอื่น ๆ น่าจะ SIG_DFL
เป็นตัวจัดการสัญญาณเริ่มต้นสำหรับสัญญาณ) เนื่องจากการเรียกครั้งแรกไปที่ 'สัญญาณ ()' ตั้งค่าตัวจัดการไปที่SIG_IGN
และsignal()
ส่งกลับตัวจัดการข้อผิดพลาดก่อนหน้านี้ค่าของคำสั่งold
หลังif
จึงต้องเป็นSIG_IGN
- ดังนั้นการยืนยัน (อาจเป็นได้SIG_ERR
ถ้ามีอะไรผิดพลาดอย่างมาก - แต่ถ้าอย่างนั้นฉันก็จะเรียนรู้เรื่องนั้นจากการยิงยืนยัน)
จากนั้นโปรแกรมจะทำสิ่งต่าง ๆ และออกจากโปรแกรมตามปกติ
โปรดทราบว่าชื่อของฟังก์ชั่นสามารถถือเป็นตัวชี้ไปยังฟังก์ชั่นของประเภทที่เหมาะสม เมื่อคุณไม่ใช้วงเล็บการเรียกใช้ฟังก์ชันเช่นใน initializers ตัวอย่างเช่นชื่อฟังก์ชันจะกลายเป็นตัวชี้ฟังก์ชัน นี่คือเหตุผลว่าทำไมจึงสมเหตุสมผลในการเรียกใช้ฟังก์ชันผ่านpointertofunction(arg1, arg2)
สัญกรณ์; เมื่อคุณเห็นalarm_handler(1)
คุณสามารถพิจารณาว่าalarm_handler
เป็นตัวชี้ไปยังฟังก์ชั่นและดังนั้นจึงalarm_handler(1)
เป็นการอุทธรณ์ของฟังก์ชั่นผ่านตัวชี้ฟังก์ชั่น
ดังนั้นจนถึงตอนนี้ฉันได้แสดงให้เห็นว่าSignalHandler
ตัวแปรค่อนข้างตรงไปข้างหน้าเพื่อใช้ตราบใดที่คุณมีประเภทของค่าที่เหมาะสมที่จะกำหนดให้กับมันซึ่งเป็นสิ่งที่ฟังก์ชั่นจัดการสัญญาณทั้งสองมีให้
ตอนนี้เรากลับไปที่คำถาม - การประกาศสองสิ่งที่signal()
เกี่ยวข้องกันอย่างไร
ตรวจสอบการประกาศที่สอง:
extern SignalHandler signal(int signum, SignalHandler handler);
หากเราเปลี่ยนชื่อฟังก์ชั่นและประเภทดังนี้:
extern double function(int num1, double num2);
คุณจะไม่มีปัญหาในการตีความว่านี่เป็นฟังก์ชั่นที่รับint
และdouble
เป็นอาร์กิวเมนต์และส่งคืนdouble
ค่า (คุณหรือไม่บางทีคุณควรจะไม่ยอมรับถ้ามันเป็นปัญหา - แต่คุณควรระวังเกี่ยวกับการถามคำถามอย่างหนัก อย่างนี้ถ้ามันเป็นปัญหา)
ตอนนี้แทนที่จะเป็นdouble
ที่signal()
ฟังก์ชั่นใช้SignalHandler
เป็นอาร์กิวเมนต์ที่สองของตนและจะส่งกลับอย่างใดอย่างหนึ่งเป็นผลของมัน
กลไกที่สามารถใช้เป็น:
extern void (*signal(int signum, void(*handler)(int signum)))(int signum);
จะอธิบายได้ยาก - ดังนั้นฉันอาจจะทำให้เสีย ครั้งนี้ฉันได้รับชื่อพารามิเตอร์ - แม้ว่าชื่อจะไม่สำคัญ
โดยทั่วไปใน C กลไกการประกาศเป็นเช่นนั้นถ้าคุณเขียน:
type var;
จากนั้นเมื่อคุณเขียนมันหมายถึงค่าที่กำหนดvar
type
ตัวอย่างเช่น:
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
ในมาตรฐานtypedef
จะถือว่าเป็นคลาสหน่วยเก็บข้อมูลในไวยากรณ์แทนที่จะเป็นstatic
และextern
เป็นคลาสหน่วยเก็บข้อมูล
typedef void (*SignalHandler)(int signum);
หมายความว่าเมื่อคุณเห็นตัวแปรประเภทSignalHandler
(เรียกว่า alarm_handler) เรียกใช้เป็น:
(*alarm_handler)(-1);
ผลลัพธ์มีtype void
- ไม่มีผลลัพธ์ และ(*alarm_handler)(-1);
เป็นอุทธรณ์ของกับการโต้แย้งalarm_handler()
-1
ดังนั้นถ้าเราประกาศ:
extern SignalHandler alt_signal(void);
หมายความว่า:
(*alt_signal)();
แทนค่าที่เป็นโมฆะ และดังนั้นจึง:
extern void (*alt_signal(void))(int signum);
เทียบเท่า ตอนนี้signal()
มีความซับซ้อนมากขึ้นเพราะไม่เพียง แต่จะส่งกลับSignalHandler
ก็ยังยอมรับทั้ง int และSignalHandler
เป็นข้อโต้แย้ง:
extern void (*signal(int signum, SignalHandler handler))(int signum);
extern void (*signal(int signum, void (*handler)(int signum)))(int signum);
ถ้านั่นยังทำให้คุณงงฉันไม่แน่ใจว่าจะช่วยได้อย่างไร - มันยังอยู่ในระดับลึกลับสำหรับฉัน แต่ฉันโตขึ้นเรื่อย ๆ กับวิธีการทำงานและสามารถบอกคุณได้ว่าถ้าคุณติดอยู่กับมันอีก 25 ปี หรือเป็นเช่นนั้นมันจะกลายเป็นเรื่องที่สองสำหรับคุณ (และอาจเร็วขึ้นเล็กน้อยถ้าคุณฉลาด)