Bu C işlevi her zaman yanlış döndürmelidir, ancak


317

Uzun zaman önce bir forumdaki ilginç bir soruyu tökezledim ve cevabı bilmek istiyorum.

Aşağıdaki C işlevini göz önünde bulundurun:

f1.c

#include <stdbool.h>

bool f1()
{
    int var1 = 1000;
    int var2 = 2000;
    int var3 = var1 + var2;
    return (var3 == 0) ? true : false;
}

O falsezamandan beri bu her zaman dönmelidir var3 == 3000. mainFonksiyonu aşağıdaki gibidir:

main.c

#include <stdio.h>
#include <stdbool.h>

int main()
{
    printf( f1() == true ? "true\n" : "false\n");
    if( f1() )
    {
        printf("executed\n");
    }
    return 0;
}

Yana f1()hep dönmelidir false, tek bir program yalnızca bir yazdırma beklenebilir false ekranına. Ancak derledikten ve çalıştırdıktan sonra, yürütüldü :

$ gcc main.c f1.c -o test
$ ./test
false
executed

Neden? Bu kod bir tür tanımsız davranışa sahip mi?

Not: ile derledim gcc (Ubuntu 4.9.2-10ubuntu13) 4.9.2.


9
Diğerleri işlevleriniz ayrı dosyalarda olduğu için bir prototipe ihtiyacınız olduğunu belirtmişlerdir. Ancak f1(), aynı dosyaya main()kopyalasanız bile, biraz tuhaflık elde edersiniz: C ++ ' ()da boş bir parametre listesi için kullanmak doğru olsa da, C henüz tanımlanmamış bir parametre listesine sahip bir işlev için kullanılır ( temelde )) sonrasında bir K & R stili parametre listesi bekler . Doğru C olması için kodunuzu olarak değiştirmelisiniz bool f1(void).
uliwitness

1
main()Basitleştirilmiş olabilir int main() { puts(f1() == true ? "true" : "false"); puts(f1() ? "true" : "false"); return 0; }bu daha iyi tutarsızlık gösterecektir -.
Palec

@uliwitness P&R 1st ed. (1978) ne zaman yoktu void?
Ho1

@uliwitness K&R 1. baskıda yoktu trueve falsehiç böyle bir sorun yoktu. Doğru ve yanlış için sadece 0 ve sıfırdan farklıydı. Öyle değil mi? O zaman prototiplerin mevcut olup olmadığını bilmiyorum.
Ho1

1
K&R 1st Edn, prototiplerden (ve C standardından) on yıldan fazla bir süre önceydi (standart için 1989'a karşı kitap için 1978) - gerçekten de, K&R1 yayınlandığında C ++ (Sınıflar ile C) hala ileride idi. Ayrıca, C99'dan önce _Booltip ve <stdbool.h>başlık yoktu .
Jonathan Leffler

Yanıtlar:


396

Diğer yanıtlarda belirtildiği gibi, sorun gccderleyici seçenekleri ayarlanmamış olarak kullanmanızdır. Bunu yaparsanız, varsayılan olarak 1990'dan eski C90 standardının geri çekilmiş eski standart dışı bir uygulaması olan "gnu90" adı verilir.

Eski C90 standardında C dilinde büyük bir kusur vardı: bir işlevi kullanmadan önce bir prototip bildirmediyseniz, varsayılan olarak int func ()(burada ( )"herhangi bir parametreyi kabul et" anlamına gelir). Bu, işlevin çağırma kuralını funcdeğiştirir, ancak gerçek işlev tanımını değiştirmez. Boyutu boolve intfarklı olduğu için, kod çağrıldığında işlev tanımsız davranış başlatır.

Bu tehlikeli saçmalık davranışı, 1999 yılında C99 standardının yayınlanmasıyla düzeltildi. Örtük işlev bildirimleri yasaklandı.

Ne yazık ki, 5.xx sürümüne kadar olan GCC varsayılan olarak eski C standardını kullanıyor. Muhtemelen kodunuzu standart C'den başka bir şey olarak derlemek istemeniz için hiçbir neden yoktur. Bu nedenle, GCC'ye kodunuzu 25 yaşın üzerindeki standart olmayan GNU bok yerine modern C kodu olarak derlemesi gerektiğini açıkça söylemelisiniz. .

Programınızı her zaman şu şekilde derleyerek sorunu giderin:

gcc -std=c11 -pedantic-errors -Wall -Wextra
  • -std=c11 (mevcut) C standardına (gayri resmi olarak C11 olarak bilinir) derleme için gönülsüz bir girişimde bulunmasını söyler.
  • -pedantic-errors bunu ciddiyetle yukarıdakilere yapmasını söyler ve C standardını ihlal eden yanlış kod yazdığınızda derleyici hataları verir.
  • -Wall bana iyi olabilecek bazı ekstra uyarılar vermek anlamına gelir.
  • -Wextra bana başka iyi uyarılar vermek anlamına geliyor.

19
Bu yanıt genel olarak doğrudur, ancak daha karmaşık programlar için aşağıdakilerden herhangi biri veya tümü nedeniyle -std=gnu11beklendiği gibi çalışma olasılığı daha yüksektir -std=c11: "gnu" da bulunan C11 (POSIX, X / Open, vb.) Ötesinde kütüphane işlevselliğine ihtiyaç duyma genişletilmiş modlarda bastırılmış; genişletilmiş modlarda gizlenen sistem başlıklarındaki hatalar, örneğin standart olmayan typedef'lerin kullanılabilirliği varsayılarak; yanlışlıkla trigraf kullanımı (bu standart yanlış özellik "gnu" modunda devre dışıdır).
zwol

5
Benzer nedenlerden dolayı, genellikle yüksek uyarı seviyelerinin kullanılmasını teşvik ederken, hatalar uyarı modlarının kullanımını destekleyemem. gerçek bir sorun olmasa bile orijinal yazarın testine dahil olmayan işletim sistemlerinde programların derlenememesine neden olabilir ve her ikisinden de -pedantic-errorsdaha az zahmetlidir -Werror.
zwol

7
@Lundin Aksine, bahsettiğim ikinci sorun (sistem başlıklarındaki katı uyum modlarıyla karşılaşılan hatalar) her yerde bulunur ; Kapsamlı, sistematik testler yaptım ve en az bir tane böyle bir hataya sahip olmayan yaygın olarak kullanılan işletim sistemleri yok (iki yıl önce, zaten). Sadece C11'in işlevselliğini gerektiren, başka bir ek olmaksızın C programları da benim deneyimimdeki kuraldan ziyade istisnadır.
zwol

6
@joop Standart C bool/ kullanırsanız _Bool, C kodunuzu "C ++ - esque" yolunda yazabilirsiniz; burada tüm karşılaştırmaların ve mantıksal işleçlerin boolC ++ 'da döndürdüğü varsayılırsa int, tarihsel nedenlerle . Bu, tüm bu tür ifadelerin tür güvenliğini kontrol etmek ve derleme zamanında her türlü hatayı ortaya çıkarmak için statik analiz araçlarını kullanabilmenizin büyük avantajına sahiptir. Ayrıca, kendi kendini belgeleyen kod şeklinde niyet ifade etmenin bir yoludur. Ve daha az önemlisi, aynı zamanda birkaç bayt RAM tasarrufu sağlar.
Lundin

7
C99'daki yeni şeylerin çoğunun 25 yaş üstü GNU saçmalığından geldiğini unutmayın.
Shahbaz

141

f1()Main.c için bildirilmiş bir prototipiniz yoktur , bu nedenle örtük olarak tanımlanır int f1(), yani bilinmeyen sayıda argüman alan ve bir döndüren bir işlevdir int.

Eğer intve boolfarklı boyutlara sahip, bu sonuçlanacaktır tanımsız davranış . Örneğin, makinemde int4 bayt ve boolbir bayt. İşlev geri dönmek üzere tanımlandığından , geri döndüğünde boolyığına bir bayt koyar. Ancak, örtükint olarak main.c'den döndüğü bildirildiğinden , çağıran işlev yığından 4 bayt okumaya çalışır.

Gcc'deki varsayılan derleyici seçenekleri bunu yaptığını size söylemez. Ancak derleme -Wall -Wextrayaparsanız, bunu elde edersiniz:

main.c: In function ‘main’:
main.c:6: warning: implicit declaration of function ‘f1’

Bunu düzeltmek için f1, önce main.c için bir bildirim ekleyin main:

bool f1(void);

Bağımsız değişken listesinin açık olarak ayarlandığını voidve derleyiciye, bilinmeyen sayıda bağımsız değişken anlamına gelen boş bir parametre listesinin aksine işlevin hiçbir bağımsız değişken almadığını bildirir. f1F1.c'deki tanım da bunu yansıtacak şekilde değiştirilmelidir.


2
Projelerimde yaptığım bir şey (hala GCC'yi kullandığım zaman) -Werror-implicit-function-declarationGCC'nin seçeneklerine eklendi, böylece bu artık geçmedi. Daha da iyi bir seçim, -Werrortüm uyarıları hatalara dönüştürmektir. Gösterildiğinde tüm uyarıları düzeltmeye zorlar.
uliwitness

2
Ayrıca boş parantez kullanmamalısınız çünkü bunu yapmak eskimiş bir özelliktir. Yani bu standardı C standardının bir sonraki versiyonunda yasaklayabilirler.
Lundin

1
@uliwitness Ah. Sadece C ile uğraşan C ++ 'dan gelenler için iyi bilgi
SeldomNeedy

Dönüş değeri genellikle yığına değil, bir kayıt defterine konur. Owen'ın cevabına bakın. Ayrıca, genellikle yığına bir bayt koymazsınız, ancak kelime boyutunun bir katını koyarsınız.
rsanchez

GCC'nin yeni sürümleri (5.xx) ekstra bayraklar olmadan bu uyarıyı verir.
Overv

36

Lundin'in mükemmel cevabında belirtilen boyut uyumsuzluğunun gerçekte nerede olduğunu görmek ilginç olduğunu düşünüyorum.

Derleme --save-tempsyaparsanız, bakabileceğiniz montaj dosyaları alırsınız. İşte parçası f1()yapar == 0karşılaştırma ve değerini döndürür:

cmpl    $0, -4(%rbp)
sete    %al

Geri dönen kısmı sete %al. C'nin x 86 çağrı sözleşmeler, geri dönüş 4 bayt veya daha küçük (buna değerleri intve boolkayıt listesi vasıtasıyla döndürülür) %eax. %alen düşük baytıdır %eax. Böylece, üst 3 bayt %eaxkontrolsüz bir durumda bırakılır.

Şimdi main():

call    f1
testl   %eax, %eax
je  .L2

İster bu kontroller bütün bir %eaxo int test düşünür, çünkü sıfırdır.

Açık bir işlev bildirimi eklemek aşağıdakilere değişir main():

call    f1
testb   %al, %al
je  .L2

bizim istediğimiz bu.


27

Lütfen bunun gibi bir komutla derleyin:

gcc -Wall -Wextra -Werror -std=gnu99 -o main.exe main.c

Çıktı:

main.c: In function 'main':
main.c:14:5: error: implicit declaration of function 'f1' [-Werror=impl
icit-function-declaration]
     printf( f1() == true ? "true\n" : "false\n");
     ^
cc1.exe: all warnings being treated as errors

Böyle bir mesajla, düzeltmek için ne yapacağınızı bilmelisiniz.

Düzenleme: (Şimdi silindi) bir yorum okuduktan sonra, bayraklar olmadan kodunuzu derlemeye çalıştım. Peki, bu beni derleyici hataları yerine derleyici uyarıları olmadan bağlayıcı hatalara götürdü. Ve bu bağlayıcı hataların anlaşılması daha zordur, bu yüzden -std-gnu99gerekli olmasa bile , en azından -Wall -Werrorkıçınızda çok fazla acı kurtaracak şekilde kullanmaya çalışın .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.