C'deki% n biçim belirticisinin kullanımı nedir?


125

%nC'deki biçim belirticisinin kullanımı nedir ? Birisi bir örnekle açıklayabilir mi?


25
Güzel el kitabını okumanın güzel sanatına ne oldu?
Jens

8
Bence asıl soru, böyle bir seçeneğin NOKTASI nedir? neden birisi çok daha az basılan karakter sayılarının değerini bilmek istesin, bu değeri doğrudan belleğe yazsın. Sanki geliştiriciler sıkılmış ve kernal
jia

Bu yüzden Bionic onu bıraktı.
solidak

1
Aslında bu geçerli bir sorudur ve ayrıntılı kılavuzların muhtemelen cevap vermeyeceği bir sorudur; o keşfedilmiştir %nmarkaları printfyanlışlıkla Turing tamamlama ve mesela, içinde brainfuck uygulamak görebilirsiniz github.com/HexHive/printbf ve oilshell.org/blog/2019/02/07.html#appendix-a-minor-sublanguages
John Frazer

Yanıtlar:


147

Hiçbir şey basılmadı. Argüman, o ana kadar yazılan karakterlerin sayısının depolandığı imzalı bir int işaretçisi olmalıdır.

#include <stdio.h>

int main()
{
  int val;

  printf("blah %n blah\n", &val);

  printf("val = %d\n", val);

  return 0;

}

Önceki kod yazdırılır:

blah  blah
val = 5

1
Argümanın işaretli bir int için bir işaretçi olması gerektiğini söylüyorsunuz, sonra örneğinizde imzasız bir int (muhtemelen sadece bir yazım hatası) kullandınız.
bta

1
@AndrewS: Çünkü fonksiyon değişkenin değerini değiştirecek.
Jack

3
@ Jack inther zaman imzalanır.
jamesdlin

1
@jamesdlin: Benim hatam. Üzgünüm .. Bunu nerede okuduğumu bilmiyordum.
Jack

1
Bazı nedenlerden dolayı, örnek notla hata veriyor n format specifies disabled. Sebebi nedir?
Johnny_D

186

Bu yanıtlardan çoğu ne anlatmak %n yapar (hiçbir şey yazdırmak ve bir karşı bugüne kadar basılmış karakter sayısını yazmak için hangi intdeğişkenin), ama şimdiye kadar hiç kimse gerçekten dair bir örnek vermiştir kullanmak olduda. İşte burada:

int n;
printf("%s: %nFoo\n", "hello", &n);
printf("%*sBar\n", n, "");

yazdıracak:

hello: Foo
       Bar

Foo ve Bar hizalı olarak. ( %nBu özel örneği kullanmadan bunu yapmak önemsizdir ve genel olarak kişi her zaman bu ilk printfaramayı kesebilir:

int n = printf("%s: ", "hello");
printf("Foo\n");
printf("%*sBar\n", n, "");

Biraz daha fazla rahatlığın ezoterik bir şeyi kullanmaya değip değmeyeceği %n(ve muhtemelen hatalar getirip getirmediği ) tartışmaya açıktır.)


3
Aman tanrım - bu, belirli bir yazı tipinde dizenin piksel boyutunu hesaplamanın karakter tabanlı bir sürümüdür!

& N ve * s'lerin neden gerekli olduğunu açıklayabilir misiniz? İkisi de işaretçi mi?
Andrew S

9
@AndrewS &nbir göstericidir ( &operatörün adresidir); bir işaretçi gereklidir, çünkü C, değere göre geçirilir ve bir işaretçi olmadan printf, değerini değiştiremez n. %*sİçinde kullanım printfbiçim dizesi bir yazdırır %s(bu durumda boş dize belirtici ""bir kullanarak) alan genişliğini ait nkarakterler. Temel printfilkelerin açıklaması temelde bu sorunun (ve cevabın) kapsamı dışındadır; printfSO ile ilgili belgeleri okumanızı veya kendi sorunuzu sormanızı tavsiye ederim .
jamesdlin

3
Kullanım senaryosu gösterdiğiniz için teşekkürler. İnsanların neden bu kılavuzu kopyalayıp SO'ya yapıştırıp bazen yeniden ifade ettiğini anlamıyorum. Biz insanız ve her şey, her zaman bir cevapla açıklanması gereken bir nedenle yapılır . "Hiçbir şey yapmaz", "havalı kelimesi havalı demektir" demek gibidir - neredeyse yararsız bilgi.
the_endian

1
@PSkocik Fazladan bir yönlendirme seviyesi eklemeden kıvrımlı ve hataya meyillidir.
jamesdlin

18

Tanımlayıcının pek çok pratik gerçek dünya kullanımını görmedim %n, ancak epey önce bir format dizgisi saldırısı ile oldschool printf güvenlik açıklarında kullanıldığını hatırlıyorum .

Böyle giden bir şey

void authorizeUser( char * username, char * password){

    ...code here setting authorized to false...
    printf(username);

    if ( authorized ) {
         giveControl(username);
    }
}

burada, kötü amaçlı bir kullanım biçimi dize olarak Printf geçirilen elde parametre kullanıcı adı yararlanmak ve bir arada kullanabilir %d, %cya da ağ / e çağrı yığını geçmesi ve daha sonra, bir gerçek değere yetkili değişken değiştirmek için kullanılır.

Evet, ezoterik bir kullanım, ancak güvenlik açıklarından kaçınmak için bir arka plan programı yazarken bilmek her zaman yararlıdır? : D


1
Biçim dizesi %nolarak denetlenmemiş bir girdi dizesini kullanmaktan kaçınmaktan daha fazla neden vardır printf.
Keith Thompson

13

Gönderen burada biz bugüne kadar basılmış karakter sayısını saklar görüyoruz.

n Argüman, fprintf()fonksiyonlardan birine yapılan bu çağrı ile o ana kadar çıktıya yazılan bayt sayısının içine yazıldığı bir tamsayıya bir gösterici olacaktır . Hiçbir argüman dönüştürülmez.

Örnek bir kullanım şöyle olacaktır:

int n_chars = 0;
printf("Hello, World%n", &n_chars);

n_charso zaman değerine sahip olur 12.


10

Şimdiye kadar tüm cevaplar bununla ilgili %n, ancak neden birisinin ilk etapta istemesi gerektiği değil. Depolanan değer, sonuçtaki dizeye bir dizi dizini olduğundan, sonuç dizgesini daha sonra parçalamanız veya değiştirmeniz gerektiğinde, bunun sprintf/ ile biraz yararlı snprintfolduğunu düşünüyorum. Bununla birlikte, bu uygulama çok daha kullanışlıdır, sscanfözellikle de scanfailedeki işlevler işlenen karakter sayısını değil, alan sayısını döndürdüğünden.

Başka bir hile kullanımı, başka bir işlemin parçası olarak bir sayı yazdırırken aynı anda ücretsiz bir sözde log10 almaktır.


%n"Tüm cevaplar ..." konusunda farklı düşünmeme rağmen, kullanımlarından bahsetmek için +1 . = P
jamesdlin

1
Kötü adamlar printf /% n, sprintf ve
sscanf'ı

6
@noloader: Nasıl yani? Kullanımının %n bir saldırgan için kesinlikle sıfır güvenlik açığı tehlikesi vardır. Yanlış yerleştirilmiş rezillik, %ngerçekten biçim argümanı olarak bir biçim dizesi yerine bir ileti dizesi geçirmenin aptalca uygulamasına aittir. Elbette bu durum %n, aslında kullanılan kasıtlı bir biçim dizgisinin parçası olduğunda asla ortaya çıkmaz .
R .. GitHub BUZA YARDIM ETMEYİ DURDUR

% n, belleğe yazmanıza izin verir. Sanırım saldırganın bu işaretçiyi kontrol etmediğini varsayıyorsunuz (yanılıyor olabilirim). Saldırgan işaretçiyi kontrol ederse (printf için başka bir parametre), 4 baytlık bir yazma gerçekleştirebilir. Kâr edip edemeyeceği farklı bir hikaye.
jww

8
@noloader: Bu, herhangi bir işaretçi kullanımı için doğrudur. Kimse yazdığınız için "kötü adamlara teşekkür ederim" demez *p = f();. %nİşaretçinin işaret ettiği nesneye bir sonuç yazmanın başka bir yolu olan, neden işaretçinin kendisini tehlikeli olarak kabul etmektense "tehlikeli" kabul edilmeli?
R .. GitHub BUZA YARDIM ETMEYİ DURDUR

10

İle ilişkili bağımsız değişken bir %nolarak değerlendirilir int*ve içinde o noktada basılan toplam karakter sayısıyla doldurulur printf.


9

Geçen gün %nsorunumu güzelce çözecek bir durumda buldum kendimi . Önceki cevabımın aksine , bu durumda iyi bir alternatif düşünemiyorum.

Belirli bir metni görüntüleyen bir GUI kontrolüm var. Bu kontrol, metnin bir bölümünü kalın (veya italik veya altı çizili vb.) Olarak görüntüleyebilir ve başlangıç ​​ve bitiş karakter indekslerini belirterek hangi bölümü belirtebilirim.

Benim durumumda, ile kontrol için metin snprintfoluşturuyorum ve ikamelerden birinin kalın yapılmasını istiyorum. Bu ikamenin başlangıç ​​ve bitiş endekslerini bulmak önemsiz değildir çünkü:

  • Dize birden çok ikame içerir ve ikamelerden biri rastgele, kullanıcı tarafından belirlenmiş metindir. Bu, ilgilendiğim ikame için metinsel bir arama yapmanın potansiyel olarak belirsiz olduğu anlamına gelir.

  • Biçim dizesi yerelleştirilebilir ve $konumsal biçim belirleyicileri için POSIX uzantısını kullanabilir . Bu nedenle, orijinal biçim dizesini biçim belirleyicilerin kendileri için aramak önemsiz değildir.

  • Yerelleştirme yönü aynı zamanda biçim dizesini birden çok çağrıya kolayca bölemeyeceğim anlamına gelir snprintf.

Bu nedenle, belirli bir ikamenin etrafındaki endeksleri bulmanın en kolay yolu aşağıdakileri yapmak olacaktır:

char buf[256];
int start;
int end;

snprintf(buf, sizeof buf,
         "blah blah %s %f yada yada %n%s%n yakety yak",
         someUserSpecifiedString,
         someFloat,
         &start, boldString, &end);
control->set_text(buf);
control->set_bold(start, end);

Kullanım alanı için size +1 vereceğim. Ancak bir denetimde başarısız olacaksınız, bu nedenle kalın metnin başlangıcını ve sonunu işaretlemek için muhtemelen başka bir yol bulmalısınız. Yazılan karakter sayısını döndürdüğü için snprintfdönüş değerlerinin kontrol edilmesi gayet iyi çalışacak gibi görünse de üç gibi görünüyor snprintf. Belki böyle bir şey: int begin = snprintf(..., "blah blah %s %f yada yada", ...);ve int end = snprintf(..., "%s", ...);ardından kuyruk: snprintf(..., "blah blah");.
jww

3
@jww Birden fazla aramayla ilgili sorun snprintf, değişikliklerin diğer yerel ayarlarda yeniden düzenlenebilmesidir, bu nedenle bu şekilde bölünemezler .
jamesdlin

Örnek için teşekkürler. Ancak, çıktıyı alanın hemen önünde kalın yapmak için bir uçbirim kontrol dizisi yazıp ardından bir dizi yazamaz mıydınız? Terminal kontrol dizilerini kodlamazsanız, onları da konumsal (yeniden sıralanabilir) yapabilirsiniz.
PSkocik

1
@PSkocik Eğer bir terminale çıkışı ediyoruz. Örneğin, bir Win32 zengin düzenleme denetimiyle çalışıyorsanız, geri dönüp terminal denetim dizilerini daha sonra ayrıştırmak istemediğiniz sürece bu yardımcı olmaz. Bu aynı zamanda, değiştirilen metnin geri kalanında uçbirim kontrol dizilerini onurlandırmak istediğinizi varsayar; yapmazsanız, o zaman bunları filtrelemeniz veya bunlardan kaçmanız gerekir. Onsuz yapmanın imkansız olduğunu söylemiyorum %n; Kullanmanın %nalternatiflerden daha basit olduğunu iddia ediyorum .
jamesdlin

1

Hiçbir şey yazdırmıyor. %nBiçim dizesinde görünmeden önce kaç karakterin yazdırıldığını bulmak ve bunu sağlanan int'e çıkarmak için kullanılır:

#include <stdio.h>

int main(int argc, char* argv[])
{
    int resultOfNSpecifier = 0;
    _set_printf_count_output(1); /* Required in visual studio */
    printf("Some format string%n\n", &resultOfNSpecifier);
    printf("Count of chars before the %%n: %d\n", resultOfNSpecifier);
    return 0;
}

( Belgeler_set_printf_count_output )


0

Bu printf()işlevde şimdiye kadar yazdırılan karakterlerin sayısını saklayacaktır .

Misal:

int a;
printf("Hello World %n \n", &a);
printf("Characters printed so far = %d",a);

Bu programın çıktısı

Hello World
Characters printed so far = 12

kodunuzu denediğimde bana şunu verir: Merhaba Dünya Karakterleri şu ana kadar basılmıştır = 36 ,,,,, neden 36?! Windows makinesinde 32bit GCC kullanıyorum.
Sina Karvandi

-6

% n C99'dur, VC ++ ile çalışmaz.


2
%nC89'da vardı. MSVC ile çalışmaz çünkü Microsoft, güvenlik endişeleri nedeniyle varsayılan olarak devre dışı bırakmıştır; _set_printf_count_outputetkinleştirmek için önce aramanız gerekir . (Merlyn Morgan-Graham'ın cevabına bakın.)
jamesdlin

Hayır, C89 bu özelliği / arka kapı sorununu tanımlamaz. Bkz. K & R + ANSI-C amazon.com/Programming-Language-2nd-Brian-Kernighan/dp/… ?? yorumlar için URL etiketleyici nerede?
user411313

4
Sadece yanılıyorsun. printfK&R, 2. baskı Ek B Tablo B-1'de ( dönüşümler) açıkça listelenmiştir . (Kopyamın 244. sayfası) Veya ISO C90 spesifikasyonunun 7.9.6.1 bölümüne (sayfa 134) bakın.
jamesdlin

Android ayrıca% n tanımlayıcısını da kaldırdı.
jww

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.