C standardı söz konusu olduğunda, farklı türden bir işlev işaretçisine bir işlev işaretçisi atarsanız ve sonra onu çağırırsanız, bu tanımsız bir davranıştır . Ek J.2'ye bakınız (bilgilendirici):
Aşağıdaki durumlarda davranış tanımsızdır:
- Bir işaretçi, türü işaret edilen türle (6.3.2.3) uyumlu olmayan bir işlevi çağırmak için kullanılır.
Bölüm 6.3.2.3, paragraf 8 şunu okur:
Bir türdeki bir işleve yönelik bir işaretçi, başka türden bir işleve bir göstericiye dönüştürülebilir ve tekrar geri alınabilir; sonuç orijinal göstericiye eşit olmalıdır. Dönüştürülmüş bir işaretçi, türü işaret edilen yazı ile uyumlu olmayan bir işlevi çağırmak için kullanılırsa, davranış tanımsızdır.
Yani başka bir deyişle, bir işlev işaretçisini farklı bir işlev işaretçisi tipine çevirebilir, onu tekrar geri çevirebilir ve çağırabilirsiniz ve işler çalışacaktır.
Uyumluluğun tanımı biraz karmaşıktır. Bölüm 6.7.5.3, paragraf 15'te bulunabilir:
İki işlev türünün uyumlu olması için, her ikisi de uyumlu dönüş türlerini ( 127) belirtmelidir .
Ayrıca, parametre tipi listeleri, eğer her ikisi de mevcutsa, parametre sayısı ve elips sonlandırıcının kullanımında uyuşmalıdır; karşılık gelen parametrelerin uyumlu tipleri olacaktır. Bir türün bir parametre türü listesi varsa ve diğer tür, bir işlev tanımının parçası olmayan ve boş bir tanımlayıcı listesi içeren bir işlev tanımlayıcı tarafından belirtilmişse, parametre listesi bir üç nokta sonlandırıcısına sahip olmayacak ve her bir parametrenin türü varsayılan bağımsız değişken yükseltmelerinin uygulanmasından kaynaklanan türle uyumlu olmalıdır. Bir türün bir parametre türü listesi varsa ve diğer tür bir (muhtemelen boş) bir tanımlayıcı listesi içeren bir işlev tanımıyla belirtilmişse, her ikisi de parametre sayısı konusunda hemfikir olmalıdır, ve her prototip parametresinin türü, varsayılan argüman yükseltmelerinin karşılık gelen tanımlayıcının türüne uygulanmasından kaynaklanan türle uyumlu olacaktır. (Tür uyumluluğunun ve bir bileşik türün belirlenmesinde, işlev veya dizi türüyle bildirilen her parametre, ayarlanmış türe sahip olarak alınır ve nitelenmiş türle bildirilen her parametre, bildirilen türünün nitelenmemiş sürümüne sahip olarak alınır.)
127) Her iki işlev türü de "eski stil" ise, parametre türleri karşılaştırılmaz.
İki türün uyumlu olup olmadığını belirleme kuralları bölüm 6.2.7'de açıklanmıştır ve oldukça uzun oldukları için burada alıntı yapmayacağım, ancak bunları C99 standardının (PDF) taslağında okuyabilirsiniz .
Buradaki ilgili kural bölüm 6.7.5.1, 2. paragraftadır:
İki işaretçi tipinin uyumlu olması için, her ikisi de aynı nitelikte olmalı ve her ikisi de uyumlu tiplere işaretçi olacaktır.
Bu nedenle, a , a ile void* uyumlu olmadığındanstruct my_struct* , türündeki void (*)(void*)bir işlev işaretçisi, türündeki bir işlev işaretçisi ile uyumlu değildir void (*)(struct my_struct*), bu nedenle işlev işaretçilerinin bu dökümü teknik olarak tanımlanmamış bir davranıştır.
Uygulamada, yine de, bazı durumlarda işlev işaretlerini atarak güvenli bir şekilde kurtulabilirsiniz. X86 çağırma kuralında, bağımsız değişkenler yığın üzerinde itilir ve tüm işaretçiler aynı boyuttadır (x86'da 4 bayt veya x86_64'te 8 bayt). Bir işlev işaretçisi çağırmak, yığındaki bağımsız değişkenleri itmek ve işlev işaretçisi hedefine dolaylı bir sıçrama yapmak için aşağıya doğru kaynar ve açık bir şekilde makine kodu düzeyinde türler kavramı yoktur.
Kesinlikle yapamayacağınız şeyler:
- Farklı arama kurallarının işlev işaretçileri arasında geçiş yapın. Yığını alt üst edeceksiniz ve en iyi ihtimalle, çarpacaksınız, en kötü ihtimalle, devasa bir güvenlik deliği ile sessizce başarılı olacaksınız. Windows programlamada, genellikle işlev işaretçileri dolaştırırsınız. Win32 tüm geri arama fonksiyonları kullanmak beklediğini
stdcallçağırma kuralını (ki makro CALLBACK, PASCALve WINAPItüm genişletmek). Standart C çağırma kuralını ( cdecl) kullanan bir işlev göstericisini iletirseniz, kötülük ortaya çıkar.
- C ++ 'da, sınıf üyesi işlev işaretçileri ve normal işlev işaretçileri arasında dönüşüm yapın. Bu genellikle C ++ yeni başlayanları tetikler. Sınıf üyesi işlevlerinin gizli bir
thisparametresi vardır ve bir üye işlevini normal bir işleve atarsanız, thiskullanılacak nesne yoktur ve yine, çok fazla kötülük ortaya çıkar.
Bazen işe yarayabilecek ancak aynı zamanda tanımlanmamış davranış olan başka bir kötü fikir:
- İşlev işaretçileri ve düzenli işaretçiler arasında Döküm (bir döküm örneğin
void (*)(void)bir karşı void*). İşlev işaretçileri, bazı mimarilerde fazladan bağlamsal bilgiler içerebileceğinden, normal işaretçilerle aynı boyutta olmayabilir. Bu muhtemelen x86'da düzgün çalışacaktır, ancak bunun tanımsız bir davranış olduğunu unutmayın.