C ++, ben yemiyorum ne için ödeme yapıyorum?


170

C ve C ++ 'da aşağıdaki merhaba dünya örneklerini ele alalım:

main.c

#include <stdio.h>

int main()
{
    printf("Hello world\n");
    return 0;
}

main.cpp

#include <iostream>

int main()
{
    std::cout<<"Hello world"<<std::endl;
    return 0;
}

Onları montaj için godbolt içinde derlediğimde, C kodunun boyutu sadece 9 satır ( gcc -O3):

.LC0:
        .string "Hello world"
main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:.LC0
        call    puts
        xor     eax, eax
        add     rsp, 8
        ret

Ancak C ++ kodunun boyutu 22 satırdır ( g++ -O3):

.LC0:
        .string "Hello world"
main:
        sub     rsp, 8
        mov     edx, 11
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
        xor     eax, eax
        add     rsp, 8
        ret
_GLOBAL__sub_I_main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        add     rsp, 8
        jmp     __cxa_atexit

... çok daha büyük.

C ++ 'da ne yediğinizi ödediğiniz ünlüdür. Peki, bu durumda, ne için para ödüyorum?


3
Yorumlar uzun tartışmalar için değildir; bu görüşme sohbete taşındı .
Samuel Liew


26
eatC ++ ile ilişkili terimi hiç duymadım . Ne demek inanıyoruz: "Yalnızca ne için ödeme kullanmak "?
Giacomo Alzetta

7
@GiacomoAlzetta, ... yiyebileceğiniz açık büfe konseptini uygulayan bir konuşma dilidir. Daha kesin bir terimin kullanılması küresel bir kitleyle kesinlikle tercih edilir, ancak ana dili Amerikan İngilizcesi konuşmacısı olarak, başlık benim için anlamlı.
Charles Duffy

5
@ trolley813 Bellek sızıntılarının alıntı ve OP sorusuyla ilgisi yok. "Yalnızca kullandıklarınız için ödeme yaparsınız" / "Kullanmadığınız şeyler için ödeme yapmazsınız" ifadesi, belirli bir özellik / soyutlama kullanmazsanız performans isabetinin alınmadığını belirtmektir. Bellek sızıntılarının bununla hiçbir ilgisi yoktur ve bu sadece terimin eatdaha belirsiz olduğunu ve kaçınılması gerektiğini gösterir .
Giacomo Alzetta

Yanıtlar:


60

Sizin için ödediğiniz şey ağır bir kütüphane (konsola yazdırmak kadar ağır değil) çağırmaktır. Bir ostreamnesneyi başlatırsınız . Bazı gizli depolama alanları var. Sonra, std::endleşanlamlı olmayan bir çağrı \n. iostreamKütüphane birçok ayarlarını değiştirerek ve programcı ziyade işlemci üzerindeki yükü koyarak yardımcı olur. Bu sizin için ödüyoruz.

Kodu gözden geçirelim:

.LC0:
        .string "Hello world"
main:

Bir ostream nesnesi + cout'u başlatma

    sub     rsp, 8
    mov     edx, 11
    mov     esi, OFFSET FLAT:.LC0
    mov     edi, OFFSET FLAT:_ZSt4cout
    call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)

coutYeni bir satır yazdırmak ve yıkamak için tekrar arama

    mov     edi, OFFSET FLAT:_ZSt4cout
    call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
    xor     eax, eax
    add     rsp, 8
    ret

Statik depolama başlatma:

_GLOBAL__sub_I_main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        add     rsp, 8
        jmp     __cxa_atexit

Ayrıca, dil ile kütüphane arasında ayrım yapmak da şarttır.

BTW, bu hikayenin sadece bir parçası. Aradığınız işlevlerde ne yazıldığını bilmiyorsunuz.


5
Ek bir not olarak, kapsamlı testler, bir C ++ programının "ios_base :: sync_with_stdio (false);" ve "cin.tie (NULL);" cout'u printf'den daha hızlı yapar (Printf'in format dizesi yükü vardır). Birincisi ek yükü cout; printf; coutsırayla yazmasını engeller (Kendi arabellekleri olduğundan). İkinci karisiklik olacak coutve cinneden cout; cinpotansiyel olarak ilk bilgi kullanıcıya sormak için. Yıkama, yalnızca gerçekten ihtiyacınız olduğunda senkronize etmeye zorlar.
Nicholas Pipitone

Merhaba Nicholas, bu faydalı notları eklediğiniz için çok teşekkür ederim.
Arash

"dil ve kütüphane arasında ayrım yapmak önemlidir": Evet, ama bir dil ile gelen standart kütüphane her yerde mevcut olan tek kütüphanedir, bu yüzden her yerde kullanılan kütüphanedir (ve evet, C standart kütüphane bir parçasıdır C ++ belirtimine sahiptir, böylece istendiğinde kullanılabilir). "Aradığınız işlevlerde ne yazıldığını bilmiyorsunuz": Gerçekten bilmek istiyorsanız statik olarak bağlayabilirsiniz ve gerçekten de incelediğiniz arama kodu muhtemelen önemsizdir.
Peter - Monica'yı

211

Peki, bu durumda, ne için para ödüyorum?

std::coutdaha güçlü ve karmaşıktır printf. Yerel ayarlar, durum bilgisi olan biçimlendirme bayrakları ve daha fazlasını destekler.

O, kullanımını gerekmiyorsa std::printfveya std::puts- Onlar kullanılabilir in <cstdio>.


C ++ 'da ne yediğinizi ödediğiniz ünlüdür.

Ayrıca C ++ ! = C ++ Standart Kütüphanesi'ni açıklığa kavuşturmak istiyorum . Standart Kütüphane genel amaçlı ve "yeterince hızlı" olmalıdır, ancak genellikle ihtiyacınız olan şeylerin özel bir uygulamasından daha yavaş olacaktır.

Öte yandan, C ++ dili, gereksiz ekstra gizli maliyetler ödemeden kod yazmayı mümkün kılmaya çalışır (örneğin, kaydolma virtual, çöp toplama yok).


4
Standart Kütüphane'nin genel amaçlı ve "yeterince hızlı" olması gerektiğini söyleyen +1 , ancak genellikle ihtiyacınız olan şeylerin özel bir uygulamasından daha yavaş olacaktır. Birçoğu, performans etkilerini kendiniz yuvarlamaya karşı düşünmeden STL bileşenlerini açıkça kullanıyor gibi görünüyor.
Craig Estey

7
@Craig OTOH, standart kütüphanenin birçok bölümü, genellikle bunun yerine üretebileceğinden daha hızlı ve daha doğrudur.
Peter - Monica'yı

2
@ PeterA.Schneider OTOH, STL sürümü 20x-30x daha yavaş olduğunda, kendi yuvarlanmanız iyi bir şeydir. Cevabımı burada görebilirsiniz: codereview.stackexchange.com/questions/191747/… Burada, diğerleri de [en azından kısmi] kendinize ait bir rulo önerdi.
Craig Estey

1
@CraigEstey Bir vektör (belirli bir örnekle nihayetinde ne kadar iş yapılacağına bağlı olarak önemli olabilecek ilk dinamik ayırmanın dışında) bir C dizisinden daha az verimli değildir; o olduğu tasarlanmış olmamak. Etrafına kopyalanmamasına, başlangıçta yeterli yer ayırmaya vb. Dikkat edilmelidir, ancak bunların hepsi bir dizi ile de yapılmalıdır ve daha az güvenli olmalıdır. Bağlantılı örneğinizle ilgili olarak: Evet, vektörlerin bir vektörü (optimize edilmedikçe) bir 2D diziye kıyasla ekstra bir dolaylamaya neden olacaktır, ancak 20x verimliliğinin orada değil algoritmada köklü olduğunu varsayıyorum.
Peter - Monica'yı eski

174

C ve C ++ karşılaştırmıyorsunuz. Sen karşılaştırdığınız printfve std::coutfarklı şeyler (yerel, durum bilgisi biçimlendirme, vs) yeteneğine sahip olan.

Karşılaştırma için aşağıdaki kodu kullanmaya çalışın. Godbolt her iki dosya için aynı montajı oluşturur (gcc 8.2, -O3 ile test edilmiştir).

main.c:

#include <stdio.h>

int main()
{
    int arr[6] = {1, 2, 3, 4, 5, 6};
    for (int i = 0; i < 6; ++i)
    {
        printf("%d\n", arr[i]);
    }
    return 0;
}

main.cpp:

#include <array>
#include <cstdio>

int main()
{
    std::array<int, 6> arr {1, 2, 3, 4, 5, 6};
    for (auto x : arr)
    {
        std::printf("%d\n", x);
    }
}


Eşdeğer kodu göstermek ve sebebini açıklamak için şerefe.
HackSlash

134

Girişleriniz gerçekten elma ve portakalları karşılaştırıyor, ancak diğer cevapların çoğunda ima edilen sebepten dolayı değil.

Kodunuzun gerçekte ne yaptığını kontrol edelim:

C:

  • tek bir dize yazdırmak, "Hello world\n"

C ++:

  • dizeyi "Hello world"içine akıtmakstd::cout
  • std::endlmanipülatör içine akışıstd::cout

Görünüşe göre C ++ kodunuz iki kat daha fazla iş yapıyor. Adil bir karşılaştırma için bunu birleştirmeliyiz:

#include <iostream>

int main()
{
    std::cout<<"Hello world\n";
    return 0;
}

… Ve birden bire montaj kodunuz mainC'lere çok benziyor:

main:
        sub     rsp, 8
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
        xor     eax, eax
        add     rsp, 8
        ret

Aslında, C ve C ++ kodunu satır satır karşılaştırabiliriz ve çok az fark vardır :

sub     rsp, 8                      sub     rsp, 8
mov     edi, OFFSET FLAT:.LC0   |   mov     esi, OFFSET FLAT:.LC0
                                >   mov     edi, OFFSET FLAT:_ZSt4cout
call    puts                    |   call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
xor     eax, eax                    xor     eax, eax
add     rsp, 8                      add     rsp, 8
ret                                 ret

Tek gerçek fark, C ++ ' operator <<da iki argüman ( std::coutve dize) ile çağırmamızdır . fprintfAkışı belirten ilk argümana sahip daha yakın bir C eşdeğeri: kullanarak bu ufak farkı bile kaldırabiliriz .

Bu _GLOBAL__sub_I_main, C ++ için oluşturulan ancak C değil için derleme kodunu bırakır . Bu, bu derleme listesinde görünen tek gerçek ek yüktür (elbette her iki dil için daha fazla görünmez ek yük vardır ). Bu kod, C ++ programının başlangıcında bazı C ++ standart kitaplık işlevlerinin bir kerelik kurulumunu gerçekleştirir.

Ancak, diğer cevaplarda açıklandığı gibi, bu iki program arasındaki ilgili fark, maintüm ağır kaldırma sahnelerin arkasında gerçekleştiğinden , işlevin montaj çıktısında bulunmaz .


21
Bu arada C çalışma zamanının da ayarlanması gerekir ve bu çağrılan bir işlevde gerçekleşir, _startancak kodu C çalışma zamanı kitaplığının bir parçasıdır. Her halükarda bu hem C hem de C ++ için olur.
Konrad Rudolph

2
@Deduplicator: Aslında, iostream kütüphanesi varsayılan olarak herhangi bir arabellekleme yapmaz std::coutve bunun yerine G / Ç'yi stdio uygulamasına (kendi arabellekleme mekanizmalarını kullanır) aktarır. Özellikle, etkileşimli bir terminale bağlandığında (bilinen), varsayılan olarak, yazarken asla tam arabelleğe alınmış çıktı görmezsiniz std::cout. Eğer iostream kütüphanesinin kendi tamponlama mekanizmalarını kullanmasını istiyorsanız, stdio ile senkronizasyonu açıkça devre dışı bırakmanız gerekir std::cout.

6
@KonradRudolph: Aslında, printfburadaki akıntıları yıkamanıza gerek yok. Aslında, ortak bir kullanım durumunda, genellikle olduğunu göreceksiniz (çıkış dosyasına yönlendirilmiş) printfdeyimi değil floş. Yalnızca çıktı satır arabelleklenmiş veya arabelleksiz olduğunda printftetik bir yıkama sağlar.

2
@PeterCordes: Doğru, temizlenmemiş çıktı arabellekleriyle engelleyemezsiniz, ancak programın girdinizi kabul ettiği ve beklenen çıktıyı göstermeden yürüdüğü sürprizle karşılaşabilirsiniz. Bunu biliyorum çünkü hata ayıklama vesilesiyle "Yardım, programım girdi sırasında asılı ama nedenini anlayamıyorum!" birkaç günlüğüne başka bir geliştirici uyumu vermişti.

2
@PeterCordes: Yaptığım argüman "ne demek istediğini yaz" - yeni satırlar çıktının nihayet kullanılabilir olması için uygun olduğunda ve endl çıktının hemen kullanılabilir olması için uygun olduğunda kullanışlıdır.

53

C ++ 'da ne yediğinizi ödediğiniz ünlüdür. Peki, bu durumda, ne için para ödüyorum?

Bu basit. Ödersiniz std::cout. "Sadece ne yediğinize para ödersiniz", "her zaman en iyi fiyatları alırsınız" anlamına gelmez. Tabii, printfdaha ucuz. Bunun std::coutdaha güvenli ve çok yönlü olduğu iddia edilebilir , bu nedenle daha yüksek maliyeti haklıdır (daha pahalıya mal olur, ancak daha fazla değer sağlar), ancak bu noktayı kaçırır. Sen kullanmıyorsun printf, kullanıyorsun , kullandığın std::coutiçin ödüyorsun std::cout. Kullanmak için ödeme yapmazsınız printf.

Buna iyi bir örnek sanal işlevlerdir. Sanal işlevlerin bazı çalışma zamanı maliyeti ve alan gereksinimleri vardır - ancak bunları yalnızca gerçekten kullanıyorsanız. Sanal işlevleri kullanmazsanız, hiçbir şey ödemezsiniz.

Birkaç açıklama

  1. C ++ kodu daha fazla montaj talimatı olarak değerlendirilse bile, yine de bir avuç talimatdır ve herhangi bir performans yükü hala gerçek G / Ç işlemleri tarafından engellenir.

  2. Aslında, bazen daha iyi "C ++ ne yemek için ödeme". Örneğin, derleyici bazı durumlarda sanal işlev çağrısının gerekli olmadığını belirleyebilir ve bunu sanal olmayan çağrıya dönüştürebilir. Bu, sanal işlevleri ücretsiz alabileceğiniz anlamına gelir . Harika değil mi?


6
Sanal işlevleri ücretsiz olarak alamazsınız. Hala bunları yazmanın ve daha sonra ne yapılması gerektiği konusunda fikrinizle eşleşmediğinde derleyicinin kodunuzdaki dönüşümünü hata ayıklamanız gerekir.
alephzero

2
@alephzero Geliştirme maliyetlerini performans maliyetleriyle karşılaştırmanın özellikle önemli olduğundan emin değilim.

Bir yumruk israfı için harika bir fırsat ... 'Fiyat' yerine 'kalori' kelimesini kullanabilirsiniz. Ondan C ++ C daha şişman olduğunu söyleyebiliriz Ya da en azından ... söz konusu belirli kod (C lehine C lehine karşı önyargılıyım, bu yüzden oldukça ötesine gidemem). Ne yazık ki. @Bilkokuya Her durumda uygun olmayabilir ama kesinlikle göz ardı edilmemesi gereken bir şey. Bu nedenle, bütün olarak geçerlidir.
Pryftan

46

"Printf için montaj listesi" printf için DEĞİLDİR, ancak kotasyonlar için (derleyici optimizasyonu türü?); printf prety koyar çok daha karmaşık ... unutma!


13
Bu, şimdiye kadarki en iyi cevaptır, çünkü diğerleri std::cout, montaj listesinde görünmeyen iç kısımları hakkında kırmızı bir ringa suratı asarlar .
Konrad Rudolph

12
Montaj listesi, yalnızca tek bir biçim dizgisi ve sıfır ekstra argüman iletirseniz , bu çağrıya puts benzeyen bir çağrı printfiçindir. (ancak xor %eax,%eaxbir varadik fonksiyona kayıtlarda sıfır FP argümanı geçirdiğimiz için hariç ). Bunların hiçbiri uygulama değildir, sadece bir işaretleyiciyi bir dizeye kütüphane fonksiyonuna geçirir. Ama evet, optimize printfetmek putsbir şey gcc sadece gereken biçim için yapmasıdır "%s"veya hiçbir dönüşümleri ve bir yeni satır ile dize uçları varken.
Peter Cordes

45

Burada bazı geçerli cevaplar görüyorum, ancak biraz daha ayrıntıya gireceğim.

Bu metnin tüm duvarından geçmek istemiyorsanız, ana sorunuzun cevabı için aşağıdaki özete atlayın.


Soyutlama

Peki, bu durumda, ne için para ödüyorum?

Soyutlama için para ödüyorsun . Daha basit ve insan dostu kod yazmanın bir bedeli vardır. Nesneye yönelik bir dil olan C ++ 'da neredeyse her şey bir nesnedir. Herhangi bir nesneyi kullandığınızda, kaputun altında her zaman üç ana şey olacaktır:

  1. Nesne oluşturma, temel olarak nesnenin kendisi ve verileri için bellek tahsisi.
  2. Nesne başlatma (genellikle bazı init()yöntemlerle). Genellikle bellek tahsisi, bu adımdaki ilk şey olarak başlık altında gerçekleşir.
  3. Nesne imhası (her zaman değil).

Kodda görmüyorsunuz, ancak bir nesneyi her kullandığınızda yukarıdaki üç şeyin hepsinin bir şekilde gerçekleşmesi gerekir. Eğer her şeyi manuel olarak yapacak olsaydınız, kod çok daha uzun olurdu.

Şimdi, soyutlama ek yükü eklemeden verimli bir şekilde yapılabilir: yöntem inlining ve diğer teknikler hem derleyiciler hem de programcılar tarafından soyutlama ek yüklerini kaldırmak için kullanılabilir, ancak bu sizin durumunuz değildir.

C ++ 'ta gerçekten neler oluyor?

İşte, ayrılmış:

  1. std::ios_baseSınıf her I / ilgili O için temel sınıftır olan başlatılır.
  2. std::coutNesne başlatılır.
  3. Dizeniz yüklenir ve iletilir std::__ostream_insert(adıyla önceden anladığınız gibi ) akışa bir dize ekleyen bir yöntemdir std::cout(temel olarak <<operatör).
  4. cout::endlde iletilir std::__ostream_insert.
  5. __std_dso_handlegeçirilir __cxa_atexit, programdan çıkmadan önce "temizlik" sorumlu bir küresel işlev olan. __std_dso_handlekendisi bu işlev tarafından kalan küresel nesneleri yeniden konumlandırmak ve yok etmek için çağrılır.

Yani C == kullanarak hiçbir şey için ödeme yapmıyor musunuz?

C kodunda çok az adım gerçekleşiyor:

  1. Dizeniz yüklenir ve kayıt putsaracılığıyla iletilir edi.
  2. puts denir.

Hiçbir yerde nesne yok, dolayısıyla hiçbir şey başlatmaya / yok etmeye gerek yok.

Ancak bu, C'deki hiçbir şey için "ödeme yapmadığınız" anlamına gelmez . Hala soyutlama için ödeme yapıyorsunuz ve aynı zamanda C standart kütüphanesinin başlatılması ve dinamik çözünürlük printfişlevi (veya aslında putsherhangi bir format dizesine ihtiyacınız olmadığı için derleyici tarafından optimize edildi) hala başlık altında gerçekleşiyor.

Bu programı saf montajda yazacak olsaydınız, şöyle bir şey olurdu:

jmp start

msg db "Hello world\n"

start:
    mov rdi, 1
    mov rsi, offset msg
    mov rdx, 11
    mov rax, 1          ; write
    syscall
    xor rdi, rdi
    mov rax, 60         ; exit
    syscall

Bu da temel olarak yalnızca write sistem çağrısını çağırmakla ve ardından sistem çağrısıyla sonuçlanır exit. Şimdi , aynı şeyi başarmak için asgari olan bu olurdu.


Özetlemek

C çok daha çıplak kemiklidir ve sadece gerekli olan minimum değeri yapar, kullanıcıya tam kontrol sağlar, bu da istedikleri her şeyi tamamen optimize edebilir ve özelleştirebilir. İşlemciye bir kayıt defterine bir dize yüklemesini ve ardından bu dizeyi kullanmak için bir kütüphane işlevini çağırmasını söylersiniz. C ++ ise çok daha karmaşık ve soyuttur . Bu, karmaşık kod yazarken çok büyük bir avantaja sahiptir ve daha kolay yazılmasına ve daha insan dostu kodlara izin verir, ancak açıkçası bir bedeli vardır. Böyle durumlarda C ++ ile karşılaştırıldığında C ++ performansında her zaman bir dezavantaj olacaktır, çünkü C ++ bu temel görevleri yerine getirmek için gerekenden daha fazlasını sunar ve bu nedenle daha fazla ek yük getirir .

Ana sorunuza cevap :

Yediğim şeyler için para ödüyor muyum?

Bu özel durumda, evet . C ++ 'ın C'den daha fazlasını sunması gereken herhangi bir şeyden yararlanmıyorsunuz, ancak bunun nedeni C ++' ın size yardımcı olabileceği basit bir kod parçasında hiçbir şey olmaması: gerçekten C ++ 'a hiç ihtiyacınız yok.


Oh, ve sadece bir şey daha!

C ++ 'ın avantajları ilk bakışta belirgin görünmeyebilir, çünkü çok basit ve küçük bir program yazdınız, ancak biraz daha karmaşık bir örneğe bakın ve farkı görün (her iki program da aynı şeyi yapar):

C :

#include <stdio.h>
#include <stdlib.h>

int cmp(const void *a, const void *b) {
    return *(int*)a - *(int*)b;
}

int main(void) {
    int i, n, *arr;

    printf("How many integers do you want to input? ");
    scanf("%d", &n);

    arr = malloc(sizeof(int) * n);

    for (i = 0; i < n; i++) {
        printf("Index %d: ", i);
        scanf("%d", &arr[i]);
    }

    qsort(arr, n, sizeof(int), cmp)

    puts("Here are your numbers, ordered:");

    for (i = 0; i < n; i++)
        printf("%d\n", arr[i]);

    free(arr);

    return 0;
}

C ++ :

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(void) {
    int n;

    cout << "How many integers do you want to input? ";
    cin >> n;

    vector<int> vec(n);

    for (int i = 0; i < vec.size(); i++) {
        cout << "Index " << i << ": ";
        cin >> vec[i];
    }

    sort(vec.begin(), vec.end());

    cout << "Here are your numbers:" << endl;

    for (int item : vec)
        cout << item << endl;

    return 0;
}

Umarım burada ne demek istediğimi açıkça görebilirsiniz. Kullandığınız daha düşük bir seviyede bellek yönetmek zorunda nasıl C Ayrıca ihbar mallocve freeEndekslemede ve boyutları hakkında daha dikkatli olmak gerekir, nasıl ve giriş alarak ve baskı sırasında çok özel olması gerekli ne.


27

Başlamak için birkaç yanlış anlama var. Birincisi, C ++ programı gelmez bunlardan 22.000 (benim şapka bu sayıyı çekti ama ballpark yaklaşık var) daha gibi, 22 talimatlar sonuçlanabilir. Ayrıca, C kodu da 9 talimatla sonuçlanmaz. Bunlar sadece gördükleriniz.

C kodunun yaptığı, görmediğiniz bir çok şey yaptıktan sonra, CRT'den bir işlevi çağırır (genellikle paylaşılan lib olarak var olmak zorunda değildir ), sonra dönüş değerini kontrol etmez veya işlemez hatalar ve kefaletler. Derleyici ve hatta gerçekten çağırmaz optimizasyon ayarlarına bağlı olarak printfancak puts, hatta daha ilkel bir şey.
Aynı işlevi aynı şekilde çağırdıysanız, C ++ 'da aynı programı (bazı görünmez init işlevleri hariç) az çok yazmış olabilirsiniz. Veya, süper-doğru olmak istiyorsanız, aynı fonksiyonun önüne eklenir std::.

Karşılık gelen C ++ kodu gerçekte aynı şey değildir. Her <iostream>şey küçük programlar için muazzam bir yük ekleyen şişman çirkin bir domuz olduğu için iyi bilinmesine rağmen ("gerçek" bir programda gerçekten çok fazla fark etmiyorsunuz), biraz daha adil bir yorum, korkunç bir şey yapmasıdır. Görmediğiniz ve işe yarayan birçok şey . Farklı sayı biçimleri ve yerel ayarlar ve ne olursa olsun, arabellekleme ve uygun hata işleme de dahil olmak üzere hemen hemen her tür gelişmenin sihirli biçimlendirmesini içerir, ancak bunlarla sınırlı değildir. Hata yönetimi? Evet, tahmin edin, bir dizenin çıktısı gerçekten başarısız olabilir ve C programının aksine, C ++ programı bunu sessizce göz ardı etmez . Ne düşünüldüğündestd::ostreamkaputun altında, ve kimse farkında olmadan, aslında oldukça hafif. Akış sözdiziminden bir tutkuyla nefret ettiğim için kullandığım gibi değil. Ama yine de, ne yaptığını düşünürseniz oldukça harika.

Ama elbette, C ++ genel olarak C olabildiğince verimli değildir . Aynı şey değildir ve değildir çünkü verimli olarak olamaz yapıyor aynı şeyi. Başka bir şey yoksa, C ++ istisnalar (ve bunları oluşturmak, işlemek veya başarısız olmak için kod) üretir ve C'nin vermediği bazı garantiler verir. Yani, elbette, bir C ++ programının biraz daha büyük olması gerekiyor. Bununla birlikte, büyük resimde, bunun hiçbir şekilde önemi yoktur. Aksine, gerçek programlar için, C ++ 'ın nadiren daha iyi performans göstermediğini gördüm çünkü bir nedenden ötürü, daha uygun optimizasyonlar için borç veriyor gibi görünüyor. Bana özellikle nedenini sorma, bilmiyorum.

En iyisi için ateşle ve unut-umut-umut yerine doğru C kodunu yazmayı önemsiyorsanız (yani hataları kontrol edersiniz ve program hataların varlığında doğru davranırsa), fark marjinaldir, eğer varsa.


16
Bu iddianın dışında çok iyi bir cevap: “Ama elbette, C ++ genel olarak C'nin olabileceği kadar verimli değildir” yanlıştır. C ++, C kadar verimli olabilir ve yeterince yüksek seviyeli kod, eşdeğer C kodundan daha verimli olabilir . Evet, C ++ istisnaları ele almak zorunda olduğu için bazı ek yüklere sahiptir, ancak modern derleyicilerde daha iyi maliyetsiz soyutlamalardan elde edilen performans kazançlarına kıyasla bunun ek yükü ihmal edilebilir.
Konrad Rudolph

Doğru anladıysam, std::coutistisnalar da attı mı?
Saher

6
@Saher: Evet, hayır, belki. std::coutBir olan std::basic_ostreamve bir o olabilir atmak ve bu edebilir , aksi takdirde olarak oluşan istisnalar rethrow yapılandırılmış bunu yoksa olabilir istisnalar yutmak. Şey, şeyler başarısız olabilir ve C ++ ve aynı zamanda C ++ standart lib (çoğunlukla) inşa böylece arızalar kolayca farkedilmez. Bu bir rahatsızlık ve bir nimettir (ama, rahatsızlıktan daha fazla nimet). C ise sadece orta parmağınızı gösterir. Bir dönüş kodunu kontrol etmezsiniz, ne olduğunu asla bilemezsiniz.
Damon

1
@KonradRudolph: Doğru, "Nadiren C ++ 'ın daha iyi performans gösterdiğini bulamadım çünkü bir nedenden ötürü, daha uygun optimizasyonlar için ödünç verilmiş gibi görünüyor. Neden özellikle bana sorma" . Neden olduğu hemen belli değil, ancak nadiren daha iyi optimize ediyor. Sebep ne olursa olsun. Tüm bunların optimizer için aynı olduğunu düşünürdünüz, ancak değil.
Damon

22

Bir hata ödüyorsun. 80'lerde, derleyiciler biçim dizelerini kontrol etmek için yeterince iyi olmadığında, operatör aşırı yüklemesi, io sırasında tip güvenliği benzetmesinin iyi bir yolu olarak görülüyordu. Bununla birlikte, banner özelliklerinin her biri başlangıçtan itibaren kötü veya kavramsal olarak iflas eder:

<İomanip>

C ++ akışının en iğrenç kısmı, bu biçimlendirme başlık kütüphanesinin varlığıdır. Durumsal ve çirkin ve hataya yatkın olmasının yanı sıra, biçimlendirmeyi akışa bağlar.

8 basamaklı sıfır dolu onaltılık işaretsiz int ve ardından bir boşluk ve ardından 3 ondalık basamaklı bir çift yazdırmak istediğinizi varsayalım. İle <cstdio>, özlü bir biçim dizesi okuyabilirsiniz. İle <ostream>, eski durumu kaydetmeniz, hizalamayı sağa ayarlamanız, dolgu karakterini ayarlamanız, dolgu genişliğini ayarlamanız, tabanı onaltılı olarak ayarlamanız, tamsayı çıkarmanız, kaydedilmiş durumu geri yüklemeniz (aksi takdirde tamsayı biçimlendirmeniz kayan biçimlendirmeyi kirletecektir), boşluk , gösterimi sabit olarak ayarlayın, hassasiyeti ayarlayın, çift ve yeni satırın çıktısını alın, ardından eski biçimlendirmeyi geri yükleyin.

// <cstdio>
std::printf( "%08x %.3lf\n", ival, fval );

// <ostream> & <iomanip>
std::ios old_fmt {nullptr};
old_fmt.copyfmt (std::cout);
std::cout << std::right << std::setfill('0') << std::setw(8) << std::hex << ival;
std::cout.copyfmt (old_fmt);
std::cout << " " << std::fixed << std::setprecision(3) << fval << "\n";
std::cout.copyfmt (old_fmt);

Operatör Aşırı Yüklemesi

<iostream> aşırı yüklenmenin nasıl kullanılamayacağının afiş çocuğu:

std::cout << 2 << 3 && 0 << 5;

Verim

std::coutbirkaç kez daha yavaştır printf(). Yaygın featurit ve sanal sevkiyat zahmetine girer.

İplik Güvenliği

Her ikisi de <cstdio> ve <iostream>her işlev çağrısı atomik olması ile parçacığı için güvenlidir. Ancak, printf()arama başına çok daha fazla şey yapılır. Aşağıdaki programı <cstdio>seçenekle çalıştırırsanız, yalnızca bir satır görürsünüz f. Çok <iostream>çekirdekli bir makinede kullanırsanız, muhtemelen başka bir şey görürsünüz.

// g++ -Wall -Wextra -Wpedantic -pthread -std=c++17 cout.test.cpp

#define USE_STREAM 1
#define REPS 50
#define THREADS 10

#include <thread>
#include <vector>

#if USE_STREAM
    #include <iostream>
#else
    #include <cstdio>
#endif

void task()
{
    for ( int i = 0; i < REPS; ++i )
#if USE_STREAM
        std::cout << std::hex << 15 << std::dec;
#else
        std::printf ( "%x", 15);
#endif

}

int main()
{
    auto threads = std::vector<std::thread> {};
    for ( int i = 0; i < THREADS; ++i )
        threads.emplace_back(task);

    for ( auto & t : threads )
        t.join();

#if USE_STREAM
        std::cout << "\n<iostream>\n";
#else
        std::printf ( "\n<cstdio>\n" );
#endif
}

Bu örneğe karşılık, çoğu insanın zaten birden fazla iş parçacığından tek bir dosya tanımlayıcıya asla yazmak için disiplin kullanmasıdır. Eh, bu durumda, bunu gözlemlemek gerekecek <iostream>yardımsever her bir kilit çekecek <<ve her >>. Oysa <cstdio>, sık sık kilitlemeyeceksiniz ve hatta kilitlememe seçeneğiniz de var.

<iostream> daha az tutarlı bir sonuç elde etmek için daha fazla kilit harcar.


2
Printf uygulamalarının çoğu yerelleştirme için son derece kullanışlı bir özelliğe sahiptir: numaralı parametreler. İki farklı dilde (İngilizce ve Fransızca gibi) bazı çıktılar üretmeniz gerekiyorsa ve kelime sırası farklıysa, aynı printf'i farklı bir biçimlendirme dizesi ile kullanabilirsiniz ve parametreleri farklı sırayla yazdırabilirsiniz.
gnasher729

2
Akışların durumsal biçimlendirmesi, ne diyeceğimi bilmediğim hataları bulmak için çok zor olmalı. Mükemmel cevap. Eğer yapabilirsem bir kereden fazla vekil.
mathreadler

6
std::coutBirkaç kez daha yavaştır printf()” - Bu iddia tüm ağ boyunca tekrarlanıyor, ancak çağlar boyunca doğru değildi. Modern IOstream uygulamaları eşit performans gösterir printf. İkincisi ayrıca, tamponlanmış akışlar ve yerelleştirilmiş IO (işletim sistemi tarafından yapılır, ancak yine de yapılır) ile başa çıkmak için dahili olarak sanal sevkiyat gerçekleştirir.
Konrad Rudolph

3
@KevinZ Ve bu harika ama fmt'nin (tek bir dizede çok sayıda farklı format) belirli güçlü yönlerini gösteren tek bir özel çağrıyı kıyaslıyor. Daha tipik kullanımda printfve coutküçülme arasındaki fark . Bu arada, bu sitede tonlarca böyle kriterler var.
Konrad Rudolph

3
@KonradRudolph Bu da doğru değil. Mikrobenç işaretler, gerçek bir programın olacağı belirli sınırlı kaynakları (kayıtlar, icache, bellek, dal tahmin edicileri gibi) tüketmedikleri için genellikle şişkinlik ve dolaylı maliyetin altını çizer. "Daha tipik bir kullanım" anlamına geldiğinde, temelde başka yerlerde önemli ölçüde daha fazla şişkinlik olduğunu söyler, ki bu iyi, ama konu dışı. Bence, performans gereksinimleriniz yoksa, C ++ ile programlamanız gerekmez.
KevinZ

18

Diğer tüm cevapların söylediklerine ek olarak , aynı şey olmadığı
gerçeği de var .std::endl'\n'

Bu maalesef yaygın bir yanlış anlamadır. std::endl"yeni satır" anlamına gelmez, "yeni satır
yazdır" anlamına gelir ve ardından akışı temizle . Kızarma ucuz değildir!

C örneğinize işlevsel olarak uygun olması için bir an için printfve arasındaki farkları tamamen göz ardı ederek, std::coutC ++ örneğiniz aşağıdaki gibi olmalıdır:

#include <iostream>

int main()
{
    std::cout << "Hello world\n";
    return 0;
}

Ve burada kızarmayı dahil ederseniz örneklerinizin nasıl olması gerektiğine dair bir örnek.

C

#include <stdio.h>

int main()
{
    printf("Hello world\n");
    fflush(stdout);
    return 0;
}

C ++

#include <iostream>

int main()
{
    std::cout << "Hello world\n";
    std::cout << std::flush;
    return 0;
}

Kodu karşılaştırırken, beğen gibi karşılaştırdığınıza ve kodunuzun ne yaptığının sonuçlarını anladığınıza dikkat etmelisiniz. Bazen en basit örnekler bile bazı insanların düşündüğünden daha karmaşıktır.


Aslında, kullanma std::endl , satır arabelleğe alınmış bir stdio akışına yeni satır yazmanın işlevsel eşdeğeridir. stdoutözellikle etkileşimli bir cihaza bağlandığında satır arabellekli veya arabelleksiz olması gerekir. Linux, satır tamponlu seçenek konusunda ısrar ediyor.

Aslında, iostream kütüphanesi yoktur satır tamponlu modu ... çizgi-tamponlama etkisine ulaşmak için bir yol kullanmak tam olarak std::endlçıkış satırsonlarına.

@Hurkyl Israr? O zaman ne işe yarar setvbuf(3)? Yoksa varsayılan satır arabellekli demek mi? Bilginize: Normalde tüm dosyalar blok belleğe alınır. Bir akış bir terminale atıfta bulunuyorsa (normalde olduğu gibi), satır arabelleğe alınır. Standart hata akışı stderr her zaman varsayılan olarak arabelleğe alınır.
Pryftan

Yeni printfsatır karakteriyle karşılaştığında otomatik olarak akmıyor mu?
bool3max

1
@ bool3max Bu bana sadece ortamımın ne yaptığını söylerdi, diğer ortamlarda farklı olabilir. En popüler uygulamaların hepsinde aynı şekilde davransa bile, bu bir yerlerde uç bir durum olduğu anlamına gelmez. Bu yüzden stanard çok önemlidir - standart, bir şeyin tüm uygulamalar için aynı olması gerekip gerekmediğini veya uygulamalar arasında değişmesine izin verilip verilmediğini belirler.
Pharap

16

Mevcut teknik cevaplar doğru olsa da, sorunun nihayetinde bu yanlış anlamadan kaynaklandığını düşünüyorum:

C ++ 'da ne yediğinizi ödediğiniz ünlüdür.

Bu sadece C ++ topluluğundan pazarlama konuşması. (Adil olmak gerekirse, her dil topluluğunda pazarlama konuşması vardır.) Bu ciddi bir şekilde güvenebileceğiniz somut bir şey anlamına gelmez.

"Kullandığınız kadar ödersiniz", bir C ++ özelliğinin yalnızca bu özelliği kullanıyorsanız ek yükü olduğu anlamına gelir. Ancak "bir özellik" tanımı sınırsız bir şekilde değildir. Çoğunlukla, birden fazla yönü olan özellikleri etkinleştirirsiniz ve yalnızca bu özelliklerin bir alt kümesine ihtiyacınız olsa da, uygulamanın özelliği kısmen getirmesi genellikle pratik veya mümkün değildir.

Genel olarak, birçok dil (tartışmasız hepsi olmasa da), farklı derecelerde başarı ile verimli olmaya çalışır. C ++ ölçeğin bir yerinde, ancak tasarımında bu hedefte mükemmel bir şekilde başarılı olmasını sağlayacak özel veya büyülü bir şey yok.


1
Kullanmadığınız bir şey için nereye ödeme yaptığınız hakkında düşünebileceğim sadece iki şey var: istisnalar ve RTTI. Ve bunun pazarlama konuşması olduğunu düşünmüyorum; C ++ temelde daha güçlü bir C'dir ve bu da "kullandığınız kadar ödemezsiniz" dir.
Rakete1111

2
@ Rakete1111 İstisnalar atmazsa, maliyetlerinin olmadığı uzun zamandır bilinmektedir. Programınız sürekli olarak atıyorsa, yeniden tasarlanmalıdır. Arıza koşulu kontrolünüz dışındaysa, durumun yanlış olmasına dayanan yöntemi çağırmadan önce, bool döndüren bir sağlık kontrolü ile durumu kontrol etmelisiniz.
schulmaster

1
@schulmaster: C ++ 'da yazılan kodun diğer dillerde yazılmış kodlarla etkileşime girmesi gerektiğinde istisnalar tasarım kısıtlamaları getirebilir, çünkü yerel olmayan kontrol aktarımları sadece modüller birbirleriyle nasıl koordinasyon yapacağını bilirse modüller arasında sorunsuz bir şekilde çalışabilir.
supercat

1
(tartışmasız hepsi olmasa da) diller etkili olmaya çalışır . Kesinlikle hepsi değil: Ezoterik programlama dilleri yeni değil / ilginç, verimli değil. esolangs.org . BrainFuck gibi bazıları ünlü olarak verimsizdir. Veya örneğin, Shakespeare Programlama Dili, tüm tamsayıları yazdırmak için minimum 227 bayt boyut (codegolf) . Üretim kullanımı için tasarlanan diller dışında, çoğu verimlilik hedefliyor, ancak bazıları (bash gibi) çoğunlukla kolaylık sağlamayı amaçlıyor ve yavaş olduğu biliniyor.
Peter Cordes

2
Peki, pazarlama ama neredeyse tamamen doğrudur. Nasıl derleyebileceğiniz gibi, yapıştırabilir <cstdio>ve dahil <iostream>edemezsiniz -fno-exceptions -fno-rtti -fno-unwind-tables -fno-asynchronous-unwind-tables.
KevinZ

11

C ++ 'da Giriş / Çıkış işlevleri zarif bir şekilde yazılmıştır ve kullanımı kolay olacak şekilde tasarlanmıştır. Birçok açıdan, C ++ 'da nesne yönelimli özellikler için bir vitrindir.

Ama aslında biraz performanstan vazgeçiyorsunuz, ancak işlevleri daha düşük bir düzeyde işlemek için işletim sisteminizin harcadığı zamana kıyasla bu önemsiz.

C ++ standardının bir parçası oldukları için C stili işlevlerine her zaman geri dönebilir veya belki de taşınabilirliği tamamen bırakabilir ve işletim sisteminize doğrudan çağrıları kullanabilirsiniz.


23
"C ++ 'da Girdi / Çıktı fonksiyonları, Cthulian doğasını ince bir kullanışlılık kaplamasının arkasında saklamak için mücadele eden çirkin canavarlardır. Birçok bakımdan modern C ++ kodunun nasıl tasarlanmayacağına dair bir vitrin". Muhtemelen daha doğru olurdu.
user673679

3
@ user673679: Çok doğru. C ++ I / O akışları ile ilgili en büyük sorun, altında ne var: gerçekten çok fazla karmaşıklık var ve onlarla hiç ilgilenen herkes ( std::basic_*streamaşağıya atıfta bulunuyorum ) gelen kulakları biliyor. Bunlar yaygın olarak genel olacak ve kalıtım yoluyla genişletilecek şekilde tasarlanmıştır; ama sonunda kimse bunu yapmadı, karmaşıklıklarından dolayı (kelimenin tam anlamıyla iostreams üzerinde yazılmış kitaplar var), o kadar çok yeni kütüphaneler doğdu (örn. destek, yoğun bakım, vb.). Hiç şüphesiz bu hatayı ödemeyi bırakacağız.
edmz

1

Diğer cevaplarda gördüğünüz gibi, genel kütüphanelere bağlandığınızda ve karmaşık kurucuları çağırdığınızda ödeme yaparsınız. Burada özel bir soru yok, daha fazla sancı. Bazı gerçek dünya yönlerine dikkat çekeceğim:

  1. Barne, verimliliğin C ++ yerine C'de kalmanın bir nedeni olmasına asla izin vermeyecek temel bir tasarım ilkesine sahipti. Bununla birlikte, bu verimlilikleri elde etmek için dikkatli olunması gerekir ve her zaman işe yarayan ancak C spesifikasyonu içinde 'teknik olarak' olmayan ara sıra verimlilikler vardır. Örneğin, bit alanlarının düzeni gerçekten belirtilmedi.

  2. Ostream'e bakmayı deneyin. Aman tanrım şişti! Orada bir uçuş simülatörü bulmak beni şaşırtmaz. Stdlib'in printf () bile genellikle yaklaşık 50K çalışır. Bunlar tembel programcılar değil: printf boyutunun yarısı çoğu insanın asla kullanmadığı dolaylı hassasiyet argümanlarıyla ilgiliydi. Hemen hemen her gerçekten kısıtlanmış işlemci kütüphanesi printf yerine kendi çıkış kodunu oluşturur.

  3. Boyuttaki artış genellikle daha kapsayıcı ve esnek bir deneyim sağlar. Bir benzetme olarak, bir satış makinesi birkaç bozuk para için bir fincan kahve benzeri madde satacak ve tüm işlem bir dakikadan az sürüyor. İyi bir restorana girmek, bir masa ayarı, oturmak, sipariş vermek, beklemek, güzel bir fincan almak, bir fatura almak, form seçiminizi ödemek, bir ipucu eklemek ve çıkışta iyi bir gün dilemek. Onun farklı bir deneyim ve karmaşık bir yemek için arkadaşlarınızla bırakarak daha uygun.

  4. İnsanlar hala ANSI C yazıyor, nadiren K&R C. Deneyimlerim, sürüklenenleri sınırlamak için birkaç yapılandırma ince ayarı kullanarak her zaman bir C ++ derleyicisi ile derliyoruz. ; daha akıllı alan paketleme ve bellek düzeni için bazı iyi argümanlar olmuştur. IMHO Bence herhangi bir dil tasarımı , Python'un Zen'i gibi bir hedefler listesiyle başlamalıdır .

Eğlenceli bir tartışma oldu. Neden sihirli, küçük, basit, zarif, eksiksiz ve esnek kütüphanelere sahip olamayacağınızı soruyorsunuz?

Cevabı yok. Bir cevap olmayacak. Cevap bu.

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.