การใช้ตัวระบุรูปแบบ% n ใน C คืออะไร?


125

การใช้ตัว%nระบุรูปแบบใน C คืออะไร? ใครช่วยอธิบายด้วยตัวอย่าง


25
ศิลปะในการอ่านคู่มือที่ดีกลายเป็นอย่างไร?
Jens

8
ฉันคิดว่าคำถามที่แท้จริงคืออะไรคือPOINTของตัวเลือกเช่นนี้? ทำไมใคร ๆ ก็อยากรู้ค่าของจำนวนถ่านที่พิมพ์น้อยกว่ามากเขียนค่านั้นลงในหน่วยความจำโดยตรง มันเหมือนกับว่านักพัฒนารู้สึกเบื่อและตัดสินใจที่จะแนะนำข้อผิดพลาดในเคอร์เนล
jia chen

นั่นเป็นเหตุผลที่ Bionic ยอมทิ้งมันไป
solidak

1
ในความเป็นจริงเป็นคำถามที่ถูกต้องและเป็นคำถามที่คู่มือที่ดีจะไม่ตอบโจทย์ มีการค้นพบว่า%nทำให้printfTuring สมบูรณ์โดยไม่ได้ตั้งใจและคุณสามารถใช้ Brainfuck ได้เช่นกันโปรดดูที่github.com/HexHive/printbfและoilhell.org/blog/2019/02/07.html#appendix-a-minor-sublanguages
John Frazer

คำตอบ:


147

ไม่มีอะไรพิมพ์ อาร์กิวเมนต์ต้องเป็นตัวชี้ไปยัง int ที่มีการลงนามซึ่งจำนวนอักขระที่เขียนจนถึงตอนนี้จะถูกเก็บไว้

#include <stdio.h>

int main()
{
  int val;

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

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

  return 0;

}

รหัสก่อนหน้านี้พิมพ์:

blah  blah
val = 5

1
คุณระบุว่าอาร์กิวเมนต์ต้องเป็นตัวชี้ไปยัง int ที่มีการลงนามจากนั้นคุณใช้ int ที่ไม่ได้ลงชื่อในตัวอย่างของคุณ (อาจเป็นเพียงการพิมพ์ผิด)
bta

1
@AndrewS: เนื่องจากฟังก์ชันจะแก้ไขค่าของตัวแปร
แจ็ค

3
@ แจ็คintเซ็นตลอด
jamesdlin

1
@jamesdlin: ความผิดพลาดของฉัน ฉันขอโทษ .. ฉันไม่รู้ว่าฉันอ่านที่ไหน
แจ็ค

1
n format specifies disabledด้วยเหตุผลบางอย่างตัวอย่างยกข้อผิดพลาดมีหมายเหตุ มีเหตุผลอะไร?
Johnny_D

186

ส่วนใหญ่คำตอบเหล่านี้อธิบายสิ่งที่%n ไม่ (ซึ่งเป็นพิมพ์อะไรและจะเขียนจำนวนตัวอักษรที่พิมพ์ป่านนี้ไปยังintตัวแปร) แต่จนถึงขณะนี้ไม่มีใครได้รับจริงๆตัวอย่างของสิ่งที่ใช้ก็มี นี่คือหนึ่ง:

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

จะพิมพ์:

hello: Foo
       Bar

โดยจัดแนว Foo และ Bar (เป็นเรื่องเล็กน้อยที่จะทำเช่นนั้นโดยไม่ใช้%nสำหรับตัวอย่างนี้โดยเฉพาะและโดยทั่วไปแล้วการprintfโทรครั้งแรกนั้นอาจแตกได้:

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

ความสะดวกสบายที่เพิ่มขึ้นเล็กน้อยนั้นคุ้มค่ากับการใช้สิ่งที่ลึกลับเช่น%n(และอาจเกิดข้อผิดพลาด) หรือไม่นั้นเปิดให้มีการถกเถียงกัน)


3
โอ้ฉัน - นี่คือเวอร์ชันที่ใช้อักขระในการคำนวณขนาดพิกเซลของสตริงในแบบอักษรที่กำหนด!

คุณช่วยอธิบายได้ไหมว่าทำไมต้อง & n และ * s ทั้งคู่เป็นตัวชี้?
Andrew S

9
@AndrewS &nเป็นตัวชี้ (&คือแอดเดรสของตัวดำเนินการ); จำเป็นต้องมีตัวชี้เนื่องจาก C เป็น pass-by-value และหากไม่มีตัวชี้printfก็ไม่สามารถแก้ไขค่าของn. การ%*sใช้งานในprintfสตริงรูปแบบจะพิมพ์ตัว%sระบุ (ในกรณีนี้คือสตริงว่าง"") โดยใช้ความกว้างของฟิลด์nอักขระ คำอธิบายprintfหลักการพื้นฐานนั้นอยู่นอกขอบเขตของคำถามนี้ (และคำตอบ) ฉันขอแนะนำให้อ่านprintfเอกสารหรือถามคำถามแยกต่างหากของคุณใน SO
jamesdlin

3
ขอขอบคุณที่แสดงกรณีการใช้งาน ฉันไม่เข้าใจว่าทำไมคนทั่วไปถึงแค่คัดลอกและวางคู่มือลงใน SO และใช้ซ้ำในบางครั้ง เราเป็นมนุษย์และทุกสิ่งล้วนทำด้วยเหตุผลที่ควรอธิบายด้วยคำตอบเสมอ "ไม่ทำอะไรเลย" ก็เหมือนกับการพูดว่า "คำว่าเท่แปลว่าเจ๋ง" - ความรู้ที่แทบไม่มีประโยชน์
the_endian

1
@PSkocik มันซับซ้อนและมีโอกาสเกิดข้อผิดพลาดได้มากพอโดยไม่ต้องเพิ่มระดับทิศทางพิเศษ
jamesdlin

18

ฉันไม่เคยเห็นการใช้%nspecifier ในโลกแห่งความเป็นจริงมากนัก แต่ฉันจำได้ว่ามันถูกใช้ในช่องโหว่ printf oldschool ด้วยการโจมตีสตริงรูปแบบในขณะที่ย้อนกลับไป

สิ่งที่เป็นเช่นนี้

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

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

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

ซึ่งผู้ใช้ที่ประสงค์ร้ายสามารถใช้ประโยชน์จากพารามิเตอร์ชื่อผู้ใช้ที่ส่งผ่านไปยัง printf เป็นสตริงรูปแบบและใช้การรวมกันของ %d ,%cหรือ W / E ที่จะไปผ่านสาย stack แล้วปรับเปลี่ยนตัวแปรที่ได้รับอนุญาตให้เป็นค่าที่แท้จริง

ใช่มันเป็นการใช้งานที่ลึกลับ แต่มีประโยชน์เสมอที่จะรู้เมื่อเขียนภูตเพื่อหลีกเลี่ยงช่องโหว่ด้านความปลอดภัย? : D


1
มีเหตุผลมากกว่าที่%nจะหลีกเลี่ยงการใช้สตริงอินพุตที่ไม่ได้เลือกเป็นprintfสตริงรูปแบบ
Keith Thompson

13

จากตรงนี้เราจะเห็นว่ามันเก็บจำนวนอักขระที่พิมพ์จนถึงตอนนี้

n อาร์กิวเมนต์จะเป็นตัวชี้ไปยังจำนวนเต็มซึ่งเขียนจำนวนไบต์ที่เขียนไปยังเอาต์พุตโดยการเรียกนี้ไปยังfprintf()ฟังก์ชันใดฟังก์ชันหนึ่ง ไม่มีการเปลี่ยนข้อโต้แย้ง

ตัวอย่างการใช้งานจะเป็น:

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

n_chars12จากนั้นก็จะมีค่า


10

จนถึงตอนนี้คำตอบทั้งหมดก็เกี่ยวกับเรื่อง%nนี้ แต่ไม่ใช่ว่าทำไมใคร ๆ ก็อยากได้ตั้งแต่แรก ฉันคิดว่ามันค่อนข้างมีประโยชน์กับsprintf/ snprintfเมื่อคุณอาจต้องแยกหรือแก้ไขสตริงผลลัพธ์ในภายหลังเนื่องจากค่าที่เก็บไว้เป็นดัชนีอาร์เรย์ในสตริงผลลัพธ์ อย่างไรก็ตามแอปพลิเคชันนี้มีประโยชน์มากกว่าsscanfโดยเฉพาะอย่างยิ่งเมื่อมีฟังก์ชันในไฟล์scanfตระกูลไม่ส่งคืนจำนวนอักขระที่ประมวลผล แต่เป็นจำนวนฟิลด์

การใช้งานที่แฮ็กจริงๆอีกอย่างหนึ่งคือการได้รับ pseudo-log10 ฟรีในเวลาเดียวกันในขณะที่พิมพ์ตัวเลขเป็นส่วนหนึ่งของการดำเนินการอื่น


+1 สำหรับการกล่าวถึงการใช้งานสำหรับ%nแม้ว่าฉันขอแตกต่างกันเกี่ยวกับ "คำตอบทั้งหมด ... " = P
jamesdlin

1
คนเลวขอบคุณสำหรับการใช้ printf /% n, sprintf และ sscanf;)
jww

6
@noloader: ยังไง? การใช้งาน %nไม่มีความเสี่ยงอย่างแท้จริงจากความเสี่ยงต่อผู้โจมตี ความน่าอับอายที่ใส่ผิดตำแหน่ง%nจริงๆเป็นของการฝึกฝนโง่ ๆ ในการส่งสตริงข้อความแทนที่จะเป็นสตริงรูปแบบเป็นอาร์กิวเมนต์รูปแบบ แน่นอนว่าสถานการณ์นี้ไม่เคยเกิดขึ้นเมื่อ%nเป็นส่วนหนึ่งของสตริงรูปแบบเจตนาที่ใช้จริง
R .. GitHub STOP HELPING ICE

% n อนุญาตให้คุณเขียนลงในหน่วยความจำ ฉันคิดว่าคุณกำลังสมมติว่าผู้โจมตีไม่ได้ควบคุมตัวชี้นั้น (ฉันอาจผิด) หากผู้โจมตีควบคุมตัวชี้ (เป็นเพียงพารามิเตอร์อื่นสำหรับ printf) เขา / เธอสามารถเขียนได้ 4 ไบต์ ไม่ว่าเขา / เธอจะได้กำไรเป็นเรื่องที่แตกต่างกัน
jww

8
@noloader: นั่นเป็นความจริงเกี่ยวกับการใช้พอยน์เตอร์ ไม่มีใครบอกว่า "คนเลวขอบคุณ" *p = f();สำหรับการเขียน เหตุใด%nซึ่งเป็นเพียงอีกวิธีหนึ่งในการเขียนผลลัพธ์ไปยังวัตถุที่ชี้โดยตัวชี้จึงถูกมองว่า "อันตราย" แทนที่จะพิจารณาว่าตัวชี้นั้นเป็นอันตราย
R .. GitHub STOP HELPING ICE

10

อาร์กิวเมนต์ที่เกี่ยวข้องกับ%nจะถือว่าเป็น an int*และเต็มไปด้วยจำนวนอักขระทั้งหมดที่พิมพ์ ณ จุดนั้นในไฟล์printf.


9

เมื่อวันก่อนฉันพบว่าตัวเองอยู่ในสถานการณ์ที่%nจะช่วยแก้ปัญหาของฉันได้เป็นอย่างดี ไม่เหมือนกับคำตอบก่อนหน้านี้ในกรณีนี้ฉันไม่สามารถคิดทางเลือกที่ดีได้

ฉันมีตัวควบคุม GUI ที่แสดงข้อความที่ระบุ การควบคุมนี้สามารถแสดงบางส่วนของข้อความนั้นเป็นตัวหนา (หรือตัวเอียงหรือขีดเส้นใต้ ฯลฯ ) และฉันสามารถระบุส่วนใดได้โดยการระบุดัชนีอักขระเริ่มต้นและสิ้นสุด

ในกรณีของฉันฉันกำลังสร้างข้อความให้กับตัวควบคุมsnprintfและฉันต้องการให้การแทนที่ตัวใดตัวหนึ่งเป็นตัวหนา การค้นหาดัชนีเริ่มต้นและสิ้นสุดของการแทนที่นี้ไม่สำคัญเนื่องจาก:

  • สตริงประกอบด้วยการแทนที่หลายครั้งและการแทนที่อย่างใดอย่างหนึ่งเป็นข้อความที่ผู้ใช้ระบุโดยพลการ ซึ่งหมายความว่าการค้นหาข้อความสำหรับสิ่งทดแทนที่ฉันสนใจอาจมีความคลุมเครือ

  • สตริงรูปแบบอาจเป็นภาษาท้องถิ่นและอาจใช้$ส่วนขยาย POSIX สำหรับตัวระบุรูปแบบตำแหน่ง ดังนั้นการค้นหาสตริงรูปแบบดั้งเดิมสำหรับตัวระบุรูปแบบเองจึงไม่สำคัญ

  • snprintfด้านการแปลก็หมายความว่าฉันไม่สามารถเลิกสตริงรูปแบบออกเป็นหลายสายไป

ดังนั้นวิธีที่ตรงไปตรงมาที่สุดในการค้นหาดัชนีรอบ ๆ การแทนที่เฉพาะคือทำ:

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);

ฉันจะให้ +1 สำหรับกรณีการใช้งาน แต่คุณกำลังจะล้มเหลวในการตรวจสอบดังนั้นคุณควรคิดวิธีอื่นในการทำเครื่องหมายจุดเริ่มต้นและจุดสิ้นสุดของข้อความตัวหนา ดูเหมือนว่าสามsnprintfในขณะที่ตรวจสอบค่าส่งคืนจะทำงานได้ดีเนื่องจากsnprintfส่งคืนจำนวนอักขระที่เขียน บางทีสิ่งที่ชอบ: int begin = snprintf(..., "blah blah %s %f yada yada", ...);และแล้วหาง:int end = snprintf(..., "%s", ...); snprintf(..., "blah blah");
jww

3
@jww ปัญหาในการsnprintfเรียกหลายสายคือการแทนที่อาจถูกจัดเรียงใหม่ในภาษาอื่นดังนั้นจึงไม่สามารถแยกออกได้เช่นนั้น
jamesdlin

ขอบคุณสำหรับตัวอย่าง แต่คุณไม่สามารถเขียนลำดับการควบคุมเทอร์มินัลเพื่อให้เอาต์พุตเป็นตัวหนาก่อนฟิลด์แล้วเขียนลำดับต่อจากนั้นได้หรือไม่? หากคุณไม่ฮาร์ดโค้ดลำดับการควบคุมเทอร์มินัลคุณสามารถทำให้เป็นตำแหน่ง (ปรับเปลี่ยนได้) ด้วย
PSkocik

1
@PSkocik หากคุณส่งออกไปยังเทอร์มินัล หากคุณกำลังทำงานอยู่ให้พูดว่า Win32 rich-edit control ซึ่งจะไม่ช่วยอะไรนอกจากคุณต้องการย้อนกลับและแยกวิเคราะห์ลำดับการควบคุมเทอร์มินัลในภายหลัง นอกจากนี้ยังถือว่าคุณต้องการให้เกียรติลำดับการควบคุมเทอร์มินัลในส่วนที่เหลือของข้อความที่ถูกแทนที่ ถ้าคุณไม่ทำคุณจะต้องกรองหรือหลีกเลี่ยงสิ่งเหล่านั้น ผมไม่ได้บอกว่ามันเป็นไปไม่ได้ที่จะทำโดย%n; ฉันอ้างว่าการใช้%nนั้นตรงไปตรงมามากกว่าทางเลือกอื่น
jamesdlin

1

มันไม่พิมพ์อะไรเลย ใช้เพื่อหาจำนวนอักขระที่พิมพ์ก่อนที่จะ%nปรากฏในสตริงรูปแบบและส่งออกไปยัง int ที่ให้มา:

#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;
}

( เอกสารประกอบสำหรับ_set_printf_count_output )


0

จะเก็บค่าของจำนวนอักขระที่พิมพ์ในprintf()ฟังก์ชันนั้น ๆ

ตัวอย่าง:

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

ผลลัพธ์ของโปรแกรมนี้จะเป็น

Hello World
Characters printed so far = 12

เมื่อฉันลองให้คุณรหัสมันให้ฉัน: ตัวอักษร Hello World ที่พิมพ์จนถึงตอนนี้ = 36 ,,,,, ทำไม 36?! ฉันใช้ GCC 32 บิตในเครื่อง windows
Sina Karvandi

-6

% n คือ C99 ไม่ทำงานกับ VC ++


2
%nมีอยู่ใน C89 ไม่ทำงานกับ MSVC เนื่องจาก Microsoft ปิดใช้งานโดยค่าเริ่มต้นเนื่องจากปัญหาด้านความปลอดภัย คุณต้องโทร_set_printf_count_outputก่อนเพื่อเปิดใช้งาน (ดูคำตอบของ Merlyn Morgan-Graham)
jamesdlin

ไม่ C89 ไม่ได้กำหนดคุณลักษณะนี้ / backdoorbug ดู K & R + ANSI-C amazon.com/Programming-Language-2nd-Brian-Kernighan/dp/… ?? URL-tagger สำหรับความคิดเห็นอยู่ที่ไหน ??
user411313

4
คุณคิดผิด แสดงไว้อย่างชัดเจนในตาราง B-1 ( printfการแปลง) ของภาคผนวก B ของ K&R ฉบับที่ 2 (หน้า 244 ของสำเนาของฉัน) หรือดูหัวข้อ 7.9.6.1 (หน้า 134) ของข้อกำหนด ISO C90
jamesdlin

Android ยังลบตัวระบุ% n
jww

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.