Menu

C'de printf: Biçim Metinleri, Belirteçler ve Float Yazdırmak

printf'in C'de gerçekte nasıl çalıştığı - biçim metni modeli, her gün kullandığınız belirteçler, hizalı çıktı için genişlik ve hassasiyet, float'ları makul biçimde yazdırmak, dönüş değeri ve printf(kullanici_girdisi)'nin neden bir güvenlik açığı olduğu.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

printf, her C programcısının öğrendiği ilk fonksiyondur ve çoğunun kılavuzunu hiç tam okumadığı fonksiyondur. On dakikaya değer, çünkü biçim metni kendi başına küçük bir dildir - ve bir belirteci yanlış yapmak bir yazım hatası değil, tanımsız davranıştır.

<stdio.h> içinde yaşar, dolayısıyla onu kullanan her program #include <stdio.h> ile başlar.

Biçim Metni Modeli

printf, biçim metnini her seferinde bir karakter dolaşır. % olmayan her şey tam olarak yazıldığı gibi yazdırılır. Bir %, sonraki argümanı yiyen ve onu istenen biçimde yazdıran bir dönüşüm belirtimi başlatır.

Argümanlar belirteçlerle kesinlikle konuma göre eşleştirilir: ilk %, count'u alır, ikincisi name'i alır vb. printf'in söz verdiğiniz şeyi geçirdiğinizi denetlemenin yolu yoktur - tipleri göremez - dolayısıyla biçim metni tutmanız gereken bir sözleşmedir. -Wall'ı açmaya değmesinin sebebi budur: GCC sabit biçim metnini derleme zamanında okur ve bir argüman eşleşmediğinde uyarır.

Hiçbir şey satır sonu zorlamaz. printf("Merhaba"), imleci a'dan sonra bırakır; hemen her biçim metninin \n ile bitmesinin sebebi budur.

Her Gün Kullandığınız Belirteçler

Beş tanesi kodun çoğunu kapsar:

BelirteçYazdırdığıÖrnek çağrıÇıktı
%dişaretli bir intprintf("%d", -42)-42
%fbir doubleprintf("%f", 2.5)2.500000
%sbir metin (char *)printf("%s", "hi")hi
%ctek bir karakterprintf("%c", 'A')A
%pbir gösterici adresiprintf("%p", (void*)&x)0x7ffd...

O parçacıkta iki kural gizlidir. Gerçek bir %, %% olarak yazılır - bir biçim metninin sonundaki tek başına bir % tanımsız davranıştır. Ve %p bir void * ister, dolayısıyla göstericiyi dönüştürün; doğrudan bir int * geçirmek, pratikte gayet iyi yazdırsa da teknik olarak tanımsızdır.

Tam tablo - her belirteç, her uzunluk değiştiricisi, scanf sütunu - biçim belirteçleri sayfasındadır. Bu sayfa uzandığınız olanlarla kalıyor.

Genişlik, Hassasiyet ve Hizalama

% ile harf arasına bayraklar, bir genişlik ve bir hassasiyet koyabilirsiniz. Bir döngüyü bir tabloya böyle dönüştürürsünüz.

Genişlik bir alt sınırdır, asla bir üst sınır değil: 123456 değeriyle %3d altı basamağın hepsini yazdırır. Hassasiyet tipe göre farklı şeyler ifade eder - %f için ondalık noktadan sonraki basamaklar, %s için en fazla karakter, %d için en az basamak.

İkisi de, bir int argüman okuyan bir * ile çalışma zamanında sağlanabilir:

width'i değiştirmek tüm tabloyu yeniden dizer. C'de hizalı sütun yazdırmanın standart yolu budur - metin doldurma yardımcısına gerek yoktur.

Float'ları Makul Biçimde Yazdırmak

Hassasiyetsiz %f her zaman noktadan sonra altı basamak yazdırır ki bu nadiren birinin istediği şeydir:

Üç seçenek ve her biri için pratik bir kural:

  • %.Nf para, ölçümler ve doğal bir ondalık sayısı olan her şey için. Para birimi için istediğiniz %.2f'tir.
  • %e bilimsel gösterim için, her zaman d.dddddde±dd.
  • %g büyüklüğü önceden bilmediğinizde. %f ya da %e'den hangisi kısaysa onu seçer ve sondaki sıfırları atar - hiç görmediğiniz bir değeri günlüklemek için en makul varsayılan.

Açıkça belirtilmeye değer bir tuzak: printf'te %lf yoktur. float argümanlar printf görmeden önce double'a yükseltilir, dolayısıyla %f ikisini de halleder ve %lf yalnızca hedefin boyutunun önemli olduğu scanf'te anlamlıdır. (C99, printf'te %lf'yi eşanlamlı olarak kabul eder, ama onu yazma alışkanlığı insanları scanf'te %f yazmaya götürür ki bu gerçekten bozulur.)

Diğer Genişliklerdeki Tam Sayılar

%d, int içindir. Daha büyük ya da işaretsiz tipler bir uzunluk değiştiricisi ister ve onları uyumsuz kullanmak bir yuvarlama hatası değil tanımsız davranıştır:

size_t için %zu, insanların en sık yanlış yaptığıdır - sizeof, int değil size_t döndürür, dolayısıyla printf("%d", sizeof x), 32 bitlik sistemlerde tesadüfen çalışan ve 64 bitlik olanlarda bozulan bir hatadır.

Dönüş Değeri

printf, yazdığı karakter sayısını ya da başarısızlıkta negatif bir sayı döndürür. Genellikle yok sayılır ve çoğunlukla bu sorunsuzdur - ama çıktının ne kadar geniş olduğunu bilmeniz gerektiğinde gerçekten yararlıdır:

İlgili sprintf ve snprintf, ekran yerine bir tampona yazar ve ürettikleri uzunluğu döndürür. Her zaman, tampon boyutunu alan ve taşamayan snprintf'i tercih edin:

snprintf, tam metnin ihtiyaç duyacağı uzunluğu döndürür, dolayısıyla tampon boyutuna eşit ya da ondan büyük bir dönüş değeri size çıktının kırpıldığını söyler.

Biçim Metni Açığı

printf'teki tek güvenlik sorunu budur ve ciddidir. Yazmadığınız veriyi asla biçim metni olarak geçirmeyin:

/* TEHLİKELİ - bunu asla yapmayın */
printf(kullanici_girdisi);

/* Doğrusu */
printf("%s", kullanici_girdisi);

kullanici_girdisi, %s içeriyorsa printf hiç geçirilmemiş bir argümanı okur ve bulduğu gösterici biçimindeki çöpün peşinden gider, genellikle çökerek. Tekrarlanan %x, yığın içeriğini ekrana döker ve bu parolaları ya da anahtarları içerebilir. Ve karakter sayısını bir argümana yazan %n, tarihsel olarak bunu keyfî bellek bozulmasına ve uzaktan kod çalıştırmaya dönüştürdü.

Kural kesindir ve hiçbir maliyeti yoktur: biçim metni her zaman sizin yazdığınız bir sabittir. Değişken olan her şey argüman olarak girer. GCC'nin -Wformat-security'si hatayı işaretler ve -Wall -Wextra içinde bulunur.

Tamponlama: Çıktı Neden Bazen Geç Belirir

printf terminale hemen yazmaz. Çıktı tamponlanır - tampon dolduğunda, bir terminalde satır sonu belirdiğinde ya da program normal biçimde çıktığında boşaltılır. İki sonucu vardır:

  • Çöken bir program, "çoktan yazdırılmış" çıktıyı kaybedebilir. printf ile hata ayıklıyorsanız ve son mesaj hiç görünmüyorsa, çökme o satırdan sonra olmuştur, öncesinde değil. fflush(stdout) ekleyin ya da hata ayıklama çıktısını tamponsuz olan stderr'e gönderin.
  • fprintf(stderr, ...), hataları yazdırmanın doğru yoludur. Tamponsuzdur ve kullanıcıların normal çıktıdan bağımsız yönlendirebileceği ayrı bir akışa gider.

Sıkça Sorulan Sorular

C'de printf nasıl çalışır?

printf bir biçim metni alır ve onu karakter karakter tarar. Sıradan karakterler olduğu gibi yazdırılır; her %, sonraki argümanlardan birini tüketip onu istenen biçimde yazdıran bir dönüşüm belirtimi başlatır. printf("%d elma\n", 5), 5 elma ve bir satır sonu yazdırır.

C'de 2 ondalık basamaklı bir float nasıl yazdırılır?

Bir hassasiyet kullanın: printf("%.2f\n", 3.14159), 3.14 yazdırır. Noktadan sonraki sayı, ondalık noktadan sonra kaç basamak geleceğidir. Hassasiyetsiz %f her zaman 6 basamak yazdırır; biçimlendirilmemiş float'ların bu kadar gürültülü görünmesinin sebebi budur.

printf neden çöp yazdırıyor ya da çöküyor?

Neredeyse her zaman belirteç ile argüman arasındaki bir uyumsuzluk. printf("%d", 3.5) ya da printf("%s", 42) tanımsız davranıştır - printf gerçek tipleri göremez, biçim metnine güvenir. GCC'nin biçimi argümanlarla karşılaştırmasını sağlayan -Wall ile derleyin.

printf(kullanici_girdisi) neden tehlikelidir?

Kullanıcının metni % dizileri içeriyorsa printf onlara uyar ve hiç geçirilmemiş argümanları okur - yığın belleğini sızdırır ve %n ile belleğe yazar bile. Bu klasik biçim metni açığıdır. Her zaman printf("%s", kullanici_girdisi) yazın.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA