printf - hata kaynağı? [kapalı]


9

printfBenim kod izleme / günlüğe kaydetme amaçlı bir sürü kullanıyorum , bu programlama hatası kaynağı buldum. Her zaman ekleme operatörünü ( <<) biraz garip bir şey olarak buldum ama bunun yerine bu hatalardan bazılarını önleyebileceğimi düşünmeye başladım.

Hiç kimse benzer bir vahiy yaşadı mı ya da ben sadece burada payet kavramak?

Bazıları puan alır

  • Şu andaki düşüncem, tip güvenliğinin printf kullanmanın yararlarından daha ağır bastığıdır. Asıl sorun biçim dizgisi ve tip-güvenli olmayan değişken fonksiyonların kullanılmasıdır.
  • Belki kullanmayacağım <<ve stl çıktı akışı varyantları ama kesinlikle benzer bir tür güvenli mekanizma kullanarak bakacağım.
  • İzleme / günlük kaydı çok koşullu ama nadiren alınan bir dal olduğu için testlerde hataları kaçırmamak için her zaman kodu çalıştırmak istiyorum.

4
printfC ++ dünyasında? Burada bir şey mi eksik?
user827992

10
@ user827992: C ++ standardının referans olarak C standart kütüphanesini içermesi eksik mi? printfC ++ 'da kullanmak tamamen yasaldır . (İyi bir fikir olup olmadığı başka bir soru.)
Keith Thompson

2
@ user827992: printfbazı avantajları vardır; cevabımı gör.
Keith Thompson

1
Bu soru oldukça sınırda. "Siz ne düşünüyorsunuz?" Soruları genellikle kapalı.
dbracey

1
@vitaut Sanırım (bahşiş için teşekkürler). Saldırgan ılımlılık yüzünden biraz şaşırdım. Programlama durumları hakkında daha fazla bilgi edinmek istediğim ilginç tartışmalara yol açmıyor.
John Leidegren

Yanıtlar:


2

printf, özellikle performansı (sprintf ve fprintf gibi) önemsediğiniz durumlarda gerçekten garip bir saldırıdır. Sanal işlevlerle ilgili küçük performans yükü nedeniyle C ++ 'a düşen insanların C'nin io'larını savunmaya devam edeceği beni sürekli şaşırtıyor.

Evet, çıktı biçimimizi, derleme zamanında% 100 bildiğimiz bir şey bulmak için, anlaşılmaz biçim kodlarını kullanarak devasa bir garip atlama tablosunun içinde çalışma zamanında zorlu bir biçim dizesini ayrıştıralım!

Elbette bu biçim kodları, temsil ettikleri türlerle eşleştirilemezdi, bu çok kolay olurdu ... ve her aradığınızda, bu (güçlü yazılan) dilin sizi% llg veya% lg olup olmadığını hatırlatırsınız. bir şeyi yazdırmak / taramak için türleri manuel olarak belirleyin ve 32 bit öncesi işlemciler için tasarlanmıştır.

C ++ 'ın format genişliği ve hassasiyetini işlemesinin hantal olduğunu ve bazı sözdizimsel şeker kullanabileceğini kabul edeceğim, ancak bu, C'nin ana io sistemi olan tuhaf hack'i savunmanız gerektiği anlamına gelmez. Her iki dilde de mutlak temeller oldukça kolaydır (yine de hata ayıklama kodu için özel bir hata işlevi / hata akışı gibi bir şey kullanmanız gerekir), ancak ılımlı durumlar C'de (örneğin, yazması kolay, ayrıştırılması / hata ayıklanması zor) ) ve C'de karmaşık vakalar imkansızdır.

(Standart kapları hiç kullanırsanız, std::cout << my_list << "\n";kendime listemin tür olduğu hata ayıklama gibi şeyler yapmanıza izin veren hızlı tempüle edilmiş bir operatör << aşırı yükleme yazın list<vector<pair<int,string> > >.)


1
Kütüphanede ++ standardı C sorun, çoğu bedenselleşmeleri uygulamak olduğunu operator<<(ostream&, T), iyi ... arayarak sprintf! Performansı sprintfoptimum değildir, ancak bundan dolayı iostreams performansı genellikle daha da kötüdür.
Jan Hudec

@JanHudec: Bu, yaklaşık on yıldır bu noktada doğru değil. Gerçek yazdırma aynı temel sistem çağrılarıyla yapılır ve C ++ uygulamaları genellikle bunun için C kütüphanelerini çağırır ... ancak bu std :: cout'u printf üzerinden yönlendirmekle aynı şey değildir.
jkerian

16

C stili printf()(veya puts()veya putchar()...) çıktıyı C ++ stili std::cout << ...çıktıyla karıştırmak güvensiz olabilir. Doğru hatırlıyorsam, ayrı tamponlama mekanizmalarına sahip olabilirler, bu nedenle çıktı istenen sırada görünmeyebilir. (Bir yorumda AProgrammer'den bahsedildiği gibi, bunu sync_with_stdioele alır).

printf()temelde tür güvensizdir. Bir bağımsız değişken için beklenen tür, dize biçimiyle belirlenir ( "%d"tanıtılan bir intveya bir şey gerektirir int, doğru sonlandırılmış bir C stili dize vb. Göstermesi gerekir), ancak yanlış türde bir argümanın iletilmesi tanımsız davranışla sonuçlanır , teşhis edilebilir bir hata değil. Gcc gibi bazı derleyiciler, tür uyuşmazlıkları hakkında oldukça iyi bir uyarı işi yaparlar, ancak bunu yalnızca biçim dizesi değişmezse veya derleme zamanında biliniyorsa (en yaygın durumdur) - ve böyle yapabilirler. dil için uyarı gerekmez. Yanlış türde bir argüman iletirseniz, keyfi olarak kötü şeyler olabilir."%s"char*

C ++ 'ın akım G / Ç, <<operatörün birçok farklı tip için aşırı yüklenmesi nedeniyle çok daha güvenli bir tiptir. std::cout << xtürünü belirtmek zorunda değildir x; derleyici hangi tipte olursa olsun doğru kodu üretecektir x.

Öte yandan, printfbiçimlendirme seçenekleri IMHO çok daha uygundur. Ondalık noktadan sonra 3 basamaklı bir kayan nokta değeri yazdırmak istersem, kullanabilirim "%.3f"- ve aynı printfçağrıda bile diğer argümanlar üzerinde hiçbir etkisi yoktur . C ++ 'lar setprecisionise akışın durumunu etkiler ve akışı önceki durumuna geri döndürmek için çok dikkatli değilseniz daha sonra çıktıyı bozabilir. (Bu benim kişisel evcil hayvanımdır; kaçınmak için temiz bir yol eksikse, lütfen yorum yapın.)

Her ikisinin de avantajları ve dezavantajları vardır. Kullanılabilirliği printfözellikle bir C arka planınız varsa ve onu daha iyi biliyorsanız veya C kaynak kodunu bir C ++ programına aktarıyorsanız kullanışlıdır. std::cout << ...C ++ için daha deyimseldir ve tür uyumsuzluklarından kaçınmak için fazla özen gerektirmez. Her ikisi de geçerli C ++ (C ++ standardı, referans olarak C standart kitaplığının çoğunu içerir).

Bu var muhtemelen kullanmak en std::cout << ...Kodunuzdaki çalışabilir diğer C ++ programcıları uğruna, ama ya biri kullanabilirsiniz - özellikle iz kodunda sen atmak için gidiyoruz.

Ve elbette hata ayıklayıcıların nasıl kullanılacağını öğrenmek için biraz zaman harcamaya değer (ancak bu bazı ortamlarda mümkün olmayabilir).


Orijinal soruda karıştırmadan bahsedilmiyor.
dbracey

1
@dbracey: Hayır, ama bunun olası bir dezavantajı olarak bahsetmeye değer olduğunu düşündüm printf.
Keith Thompson

6
Senkronizasyon sorunu için bkz std::ios_base::sync_with_stdio.
AProgrammer

1
+1 Çok iş parçacıklı bir uygulamada hata ayıklama bilgilerini yazdırmak için std :: cout kullanımı% 100 yararsızdır. En azından printf ile işlerin insan veya makine tarafından serpiştirilmesi ve ayrıştırılması mümkün değildir.
James

@James: Bunun nedeni std::cout, yazdırılan her öğe için ayrı bir çağrı kullanılması mı? Yazdırmadan önce bir dizeye bir çıktı satırı toplayarak bu sorunu çözebilirsiniz. Ve tabii ki aynı anda bir öğeyi de yazdırabilirsiniz printf; bir çağrıda bir hat (veya daha fazla) yazdırmak daha kolaydır.
Keith Thompson

2

Sorununuz büyük olasılıkla, her biri o zayıf küçük STDOUT için kendi gündemine sahip iki farklı standart çıktı yöneticisinin karışımından kaynaklanıyor. Onların uygulanmasıyla ilgili garanti vermem ve bunların dosya tanımlayıcı seçeneklerini çelişen ayarlamanız tamamen mümkündür, buna farklı şeyler yapmak hem denemeydi vb Ayrıca, yerleştirme operatörleri önemli bir over var printf: printfBunu sağlayacak:

printf("%d", SomeObject);

Halbuki <<olmayacak.

Not: Hata ayıklama için printfveya kullanmazsınız cout. Sen kullanmak fprintf(stderr, ...)ve cerr.


Orijinal soruda karıştırmadan bahsedilmiyor.
dbracey

Tabii ki bir nesnenin adresini yazdırabilirsiniz, ancak en büyük fark, bu printftür bir güvenli değildir ve şu andaki düşünce şeklimiz, tür güvenliğinin kullanımın herhangi bir yararından ağır bastığıdır printf. Sorun gerçekten biçim dizesi ve tip-güvenli olmayan değişken işlevidir.
John Leidegren

@JohnLeidegren: Peki SomeObjectya bir işaretçi değilse ? Derleyicinin temsil ettiği keyfi ikili verileri alacaksınız SomeObject.
Linuxios

Sanırım cevabını geriye okudum ... nvm.
John Leidegren

1

Akışları sevmeyen birçok grup - örneğin google - var.

http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Streams

(Tartışmayı görebilmeniz için üçgen üçgeni açın.) Bence Google C ++ stil rehberinde çok mantıklı tavsiyeler var.

Bence ödünç akışların daha güvenli olduğu, ancak printf'in okunması daha net (ve tam olarak istediğiniz biçimlendirmeyi elde etmenin daha kolay olduğu).


2
Google stil kılavuzu güzel, ancak genel amaçlı bir kılavuz için uygun olmayan birkaç öğe içeriyor . (tamam, çünkü sonuçta Google'ın Google'da / Google'da çalışan kod için kılavuzu .)
Martin Ba

1

printftip güvenlik eksikliği nedeniyle hatalara neden olabilir. Geçiş yapmadan bu adresleme birkaç yolu vardır iostream'ın <<operatör ve daha biçimlendirme karmaşık:

  • Bazı derleyiciler (GCC ve Clang gibi) isteğe bağlı olarak printfbiçim dizelerinizi printfbağımsız değişkenlere göre kontrol edebilir ve eşleşmezlerse aşağıdaki gibi uyarıları görüntüleyebilir.
    uyarı: dönüşüm 'int' türünü belirtir, ancak bağımsız değişken 'char *' türüne sahiptir
  • Typesafeprintf komut dosyası için ön işlemeden olabilir printfonları tip-güvenli kılmak için tarzı aramaları.
  • Gibi Kütüphaneler Boost.Format ve FastFormat kullandığınız izin printfbenzeri biçim dizeleri (Boost.Format en özellikle hemen hemen aynıdır printf) tutmak ise iostreams'türü emniyet ve tip genişletilebilirlik.

1

Printf sözdizimi temelde gayet iyi, bazı belirsiz yazmalar eksi. Eğer yanlış olduğunu düşünüyorsanız neden C #, Python ve diğer diller benzer yapıyı kullanıyor? C veya C ++ 'da sorun: bir dilin parçası değildir ve bu nedenle derleyici tarafından doğru sözdizimi (*) için kontrol edilmez ve hız için optimize edilirse yerel arama serisine ayrılmaz. Boyut için optimizasyon yapılırsa, printf çağrılarının daha verimli olabileceğini unutmayın! C ++ akış sözdizimi imho ama iyi bir şey. Çalışıyor, tip güvenliği var, ama ayrıntılı sözdizimi ... bleh. Yani onu kullanıyorum, ama neşe duymadan.

(*) bazı derleyiciler bu kontrolü artı hemen hemen tüm statik analiz araçlarını YAP (Lint kullanıyorum ve printf ile ilgili hiçbir sorun yaşamadım).


1
Orada Boost.Format uygun sözdizimi (birleştirir format("fmt") % arg1 % arg2 ...;tip-güvenliği). Biraz daha performans pahasına, çünkü birçok uygulamada dahili olarak sprintf çağrıları üreten stringstream çağrıları üretir.
Jan Hudec

0

printfbenim görüşüme göre, değişkenlerle başa çıkmak için CPP akışı çıktılarından çok daha esnek bir çıktı aracıdır. Örneğin:

printf ( "%d in ANSI = %c\n", j, j ); /* Perfectly valid... if a char ISN'T printing right, I'd just check the integer value to make sure it was okay. */

Ancak, CPP kullanmak isteyebilirsiniz nerede <<belirli bir kişinin verileri barındıran bir nesnenin Dökümü almak için örneğin ... Belirli bir yönteme için aşırı zaman operatörüdür, PersonData....

ostream &operator<<(ostream &stream, PersonData obj)
{
 stream << "\nName: " << name << endl;
 stream << " Number: " << phoneNumber << endl;
 stream << " Age: " << age << endl;
 return stream;
}

Bunun için söylemek çok daha etkili olurdu a(PersonData'nın bir nesnesidir)

std::cout << a;

daha:

printf ( "Name: %s\n Number: %s\n Age: %d\n", a.name, a.number, a.age );

Birincisi, kapsülleme ilkesiyle çok daha uyumludur (Özellikleri, özel üye değişkenlerini bilmenize gerek yoktur) ve ayrıca okunması daha kolaydır.


0

printfC ++ 'da kullanmanız gerekmez . Hiç. Bunun nedeni, doğru bir şekilde belirttiğiniz gibi, bunun bir hata kaynağı olması ve özel türlerin ve C ++ 'da neredeyse her şeyin özel türler olması gerektiğidir. C ++ çözümü akışlardır.

Ancak akışları herhangi ve kullanıcı tarafından görülebilir çıktılar için uygun olmayan kritik bir sorun var! Sorun çeviriler. Gettext kılavuzundan ödünç alma örneği yazmak istediğinizi söylüyor:

cout << "String '" << str << "' has " << str.size() << " characters\n";

Şimdi Alman çevirmen geliyor ve şöyle diyor: Tamam, Almanca olarak, mesaj

n Zeichen lang ist ölmek Zeichenkette ' s '

Ve şimdi başın belada, çünkü onun etrafında karıştırılmış parçalara ihtiyacı var. Birçok uygulamanın bile printfbu konuda bir sorunu olduğu söylenmelidir . Uzantıyı desteklemedikleri sürece,

printf("%2$d Zeichen lang ist die Zeichenkette '%1$s'", ...);

Boost.Format destekleri printf tarzı biçimleri ve bu özelliği vardır. Yani şöyle yazıyorsunuz:

cout << format("String '%1' has %2 characters\n") % str % str.size();

Ne yazık ki biraz performans cezası taşır, çünkü dahili olarak bir dize akışı oluşturur ve <<her biti biçimlendirmek için operatörü kullanır ve birçok uygulamada <<operatör dahili olarak çağırır sprintf. Gerçekten istenirse daha verimli uygulamanın mümkün olabileceğinden şüpheleniyorum.


-1

stlKötü ya da değil gerçeğinin yanı sıra, kodunuzda sadece 1 hataprintf seviyesi daha olası hatalar ekleyin.

Bir hata ayıklayıcı kullanın ve İstisnalar ve bunları nasıl yakalayıp atacağınız hakkında bir şeyler okuyun; gerçekte olmanız gerekenden daha ayrıntılı olmaya çalışmayın.

PS

printf C'de kullanılır, sahip olduğunuz C ++ için std::cout


Hata ayıklayıcı yerine izleme / günlük kaydı kullanmazsınız.
John Leidegren
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.