signal()
C standardındaki işlevi düşünün :
extern void (*signal(int, void(*)(int)))(int);
Tamamen anlaşılır bir şekilde açık - tamsayıyı bağımsız değişken olarak alan ve hiçbir şey döndürmeyen bir işleve iki bağımsız değişken, bir tamsayı ve bir işaretçi alan bir işlevdir ve ( signal()
) bir tamsayıyı bağımsız değişken olarak alan ve döndüren bir işleve bir işaretçi döndürür hiçbir şey değil.
Eğer yazarsanız:
typedef void (*SignalHandler)(int signum);
bunun yerine şu şekilde beyan edebilirsiniz signal()
:
extern SignalHandler signal(int signum, SignalHandler handler);
Bu aynı şey anlamına gelir, ancak genellikle okunması biraz daha kolay olarak kabul edilir. Fonksiyonun int
a SignalHandler
ve a alıp a döndürdüğü daha açıktır SignalHandler
.
Yine de alışmak biraz zaman alıyor. Yapamayacağınız bir şey SignalHandler
typedef
, işlev tanımında kullanarak bir sinyal işleyici işlevi yazmaktır .
Ben hala bir işlev işaretçisi olarak çağırmayı tercih eski okuldayım:
(*functionpointer)(arg1, arg2, ...);
Modern sözdizimi sadece şunu kullanır:
functionpointer(arg1, arg2, ...);
Neden işe yaradığını görebiliyorum - Ben sadece bir fonksiyon yerine değişkenin başlatıldığı yeri aramak gerektiğini bilmek istiyorum functionpointer
.
Sam yorumladı:
Bu açıklamayı daha önce görmüştüm. Ve sonra, şimdi olduğu gibi, almadım iki ifade arasındaki bağlantı olduğunu düşünüyorum:
extern void (*signal(int, void()(int)))(int); /*and*/
typedef void (*SignalHandler)(int signum);
extern SignalHandler signal(int signum, SignalHandler handler);
Ya da sormak istediğim şey, sahip olduğunuz ikinci versiyonu bulmak için kullanabileceğiniz temel kavram nedir? "SignalHandler" ı ve ilk typedef'i bağlayan temel unsur nedir? Burada açıklanması gereken şey, typedef'in aslında burada yaptığı şey.
Tekrar deneyelim. Bunlardan birincisi doğrudan C standardından kaldırılır - tekrar yazdım ve parantezlerin doğru olduğunu kontrol ettim (düzeltene kadar değil - hatırlanması zor bir çerez).
Her şeyden önce, typedef
bir tür için bir takma ad getirdiğini unutmayın . Yani, takma ad SignalHandler
ve türü:
bağımsız değişkeni bağımsız değişken olarak alan ve hiçbir şey döndürmeyen bir işleve işaretçi.
'Hiçbir şey döndürmez' bölümü hecelenir void
; bir tamsayı olan argüman kendime açıklayıcıdır (güveniyorum). Aşağıdaki gösterim, basitçe (veya değil) C'nin belirtildiği gibi argümanları alma ve verilen türü döndürme işlevine işaretçiyi nasıl yazdığını gösterir:
type (*function)(argtypes);
Sinyal işleyici türünü oluşturduktan sonra bunu değişkenleri bildirmek için kullanabilirim. Örneğin:
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);
}
Lütfen dikkat Bir sinyal işleyicide kullanmaktan nasıl kaçınılır printf()
?
Peki, burada ne yaptık - kodun temiz bir şekilde derlenmesi için ihtiyaç duyulacak 4 standart üstbilgiyi atlayın?
İlk iki işlev, tek bir tamsayı alan ve hiçbir şey döndürmeyen işlevlerdir. Biri aslında sayesinde exit(1);
geri dönmüyor, diğeri ise bir mesaj yazdırdıktan sonra geri dönüyor. C standardının bir sinyal işleyici içinde çok fazla şey yapmanıza izin vermediğini unutmayın; POSIX , izin verilen şeylerde biraz daha cömerttir, ancak resmi olarak çağrıya izin vermez fprintf()
. Alınan sinyal numarasını da yazdırıyorum. İşlevde, alarm_handler()
değer her zaman SIGALRM
bir işleyici olduğu tek sinyal olduğu gibi olacaktır, ancak her ikisi için de aynı işlev kullanıldığı için sinyal numarası olarak veya signal_handler()
alabilir .SIGINT
SIGQUIT
Daha sonra, her elemanın bir sinyal numarasını ve bu sinyal için kurulacak işleyiciyi tanımladığı yapılar dizisi oluşturuyorum. Yaklaşık 3 sinyal için endişelenmeyi seçtim; Sık sık dert ediyorum SIGHUP
, SIGPIPE
ve SIGTERM
çok onlar (tanımlanmıştır olmadığı konusunda #ifdef
koşullu derleme), ama bu sadece bir şeyler zorlaştırmaktadır. Muhtemelen sigaction()
bunun yerine POSIX kullanırdım signal()
, ama bu başka bir sorun; Başladığımız şeye sadık kalalım.
main()
İşleyicileri listesinin üzerine fonksiyon yineler kurulacak. Her işleyici için, ilk signal()
olarak işlemin şu anda sinyali yoksayıp almadığını öğrenmek için arama yapar ve bunu yaparken SIG_IGN
, işleyicinin olarak yüklenerek sinyalin yok sayılmasını sağlar. Sinyal daha önce yoksayılmamışsa signal()
, bu kez tercih edilen sinyal işleyiciyi kurmak için tekrar arar . (Diğer değer tahminen olduğu SIG_DFL
sinyali için varsayılan sinyal işleyici.) İçin ilk çağrı 'sinyali ()' olarak işleyici ayarladığımızdan SIG_IGN
ve signal()
önceki hata işleyicisi, değerini verir old
sonra if
deyim olmalıdır SIG_IGN
- bu nedenle iddiayı. (Şey, olabilirSIG_ERR
eğer bir şey önemli ölçüde yanlış giderse - ama o zaman bunu iddia ateşlemesinden öğrenirdim.)
Program daha sonra işlerini yapar ve normal olarak çıkar.
Bir işlevin adının, uygun türde bir işleve bir işaretçi olarak kabul edilebileceğini unutmayın. İşlev çağrısı parantezlerini (örneğin, başlatıcılarda olduğu gibi) uygulamadığınızda, işlev adı bir işlev işaretçisi haline gelir. Bu nedenle pointertofunction(arg1, arg2)
gösterim yoluyla işlevleri çağırmak da mantıklıdır ; gördüğünüzde alarm_handler(1)
, alarm_handler
bunun işleve bir işaretçi olduğunu ve bu nedenle alarm_handler(1)
bir işlev işaretçisi aracılığıyla bir işlevin çağrılması olduğunu düşünebilirsiniz .
Şimdiye kadar, bir SignalHandler
değişkenin, atamak için doğru değer türlerinden bazılarına sahip olduğunuz sürece, göreceli olarak basit olduğunu gösterdim - bu, iki sinyal işleyici işlevinin sağladığı şeydir.
Şimdi şu soruya dönüyoruz - iki beyan signal()
birbiriyle nasıl ilişkilidir?
İkinci beyanı inceleyelim:
extern SignalHandler signal(int signum, SignalHandler handler);
İşlev adını ve türünü şu şekilde değiştirirsek:
extern double function(int num1, double num2);
bunu bir int
ve a işlevlerini double
argüman olarak alan ve bir double
değer döndüren bir işlev olarak yorumlamakta sorun yaşamazsınız (belki de sorunluysa bu konuda karar vermemelisiniz - ama belki de bu kadar zor sorular sormakta dikkatli olmalısınız) bu bir sorun ise).
Şimdi, yerine olmanın double
, signal()
fonksiyon alır SignalHandler
ikinci argüman olarak ve onun sonucunda bir döndürür.
Bunun da işlenebileceği mekanik:
extern void (*signal(int signum, void(*handler)(int signum)))(int signum);
açıklamak zor - bu yüzden muhtemelen berbat edeceğim. Bu kez parametrelerin isimlerini verdim - isimler kritik olmasa da.
Genel olarak, C'de, beyan mekanizması şöyle yazar:
type var;
o zaman yazdığınızda var
verilen bir değeri temsil eder type
. Örneğin:
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
Standartta, typedef
dilbilgisinde bir depolama sınıfı olarak ele alınır, daha ziyade static
ve extern
depolama sınıflarıdır.
typedef void (*SignalHandler)(int signum);
şu şekilde SignalHandler
çağrılmış bir tür değişken (alarm_handler) gördüğünüzde:
(*alarm_handler)(-1);
sonuç var type void
- sonuç yok. Ve argümanla (*alarm_handler)(-1);
çağrılmasıdır .alarm_handler()
-1
Yani, eğer beyan edersek:
extern SignalHandler alt_signal(void);
demek oluyor:
(*alt_signal)();
bir boşluk değerini temsil eder. Ve bu nedenle:
extern void (*alt_signal(void))(int signum);
eşdeğerdir. Şimdi, signal()
daha karmaşık çünkü sadece a döndürmekle kalmıyor SignalHandler
, aynı zamanda hem int hem de a SignalHandler
değişkenlerini kabul ediyor :
extern void (*signal(int signum, SignalHandler handler))(int signum);
extern void (*signal(int signum, void (*handler)(int signum)))(int signum);
Bu hala sizi şaşırtıyorsa, nasıl yardım edeceğimden emin değilim - hala benim için gizemli, ama nasıl çalıştığına alıştım ve bu yüzden size 25 yıl daha yapışırsanız söyleyebilirim ya da öyleyse, bu sizin için ikinci bir doğa olacaktır (ve belki de akıllıysanız biraz daha hızlı).