การอ่านสตริงด้วย scanf


147

ฉันสับสนเล็กน้อยเกี่ยวกับบางสิ่งบางอย่าง ฉันรู้สึกว่าวิธีที่ถูกต้องในการอ่านสตริง C ที่scanf()ไปด้วยกัน

(ไม่ต้องสนใจบัฟเฟอร์ล้นที่เป็นไปได้มันเป็นเพียงตัวอย่างง่ายๆ)

char string[256];
scanf( "%s" , string );

อย่างไรก็ตามต่อไปนี้ดูเหมือนว่าจะทำงานด้วย

scanf( "%s" , &string );

นี่เป็นเพียงผู้รวบรวม (gcc) ของฉันโชคบริสุทธิ์หรืออย่างอื่นใช่ไหม


ในกรณีที่สองไม่มีบัฟเฟอร์มากเกินไปที่เป็นไปได้เนื่องจากคุณไม่ได้ใช้บัฟเฟอร์นั้นเลย ไม่เช่นนั้นหรือคุณอาจบอกว่าสตริงใด ๆ ที่มีขนาดใหญ่กว่า 3 ตัวอักษรจะล้น "บัฟเฟอร์" ของคุณ
TED

ฉันหมายถึงตัวอย่างแรก นอกจากนี้คนอื่น ๆ ได้ชี้ให้เห็นแล้วว่าเกิดอะไรขึ้นที่นี่
abeln

ได้. พยายามออกมาแล้วแกเร็ ธ พูดถูก แปลก.
TED

เนื่องจากคำถามนี้ถูกส่งกลับโดยการค้นหา "วิธีอ่านสตริงด้วย scanf" จึงอาจเหมาะสมที่จะชี้ให้เห็นว่าคำถามนี้เกี่ยวกับวิธีส่งตัวชี้ไปยังบัฟเฟอร์ไปยังscanfและทั้งคำถามและคำตอบที่ได้รับการยอมรับมุ่งเน้นไปที่ ที่และละเว้นข้อ จำกัด ที่สำคัญอย่างยิ่งสำหรับความยาวสูงสุดที่ควรใช้ในรหัสจริง (แต่อยู่นอกเหนือจากจุดสำหรับคำถามนี้)
Arkku

คำตอบ:


141

อาร์เรย์ "สูญสลาย" ลงในตัวชี้ไปยังองค์ประกอบแรกของมันเพื่อให้เทียบเท่ากับscanf("%s", string) scanf("%s", &string[0])ในขณะที่scanf("%s", &string)ผ่านตัวชี้ไป - char[256]แต่มันชี้ไปที่สถานที่เดียวกัน

จากนั้นเมื่อการประมวลผลหางของรายการอาร์กิวเมนต์ของตนจะพยายามที่จะดึงออกscanf char *นั่นคือสิ่งที่ถูกต้องเมื่อคุณผ่านstringหรือ&string[0]แต่เมื่อคุณผ่าน&stringคุณขึ้นอยู่กับสิ่งที่มาตรฐานภาษาไม่ได้รับประกันคือตัวชี้&stringและ&string[0]- ตัวชี้ไปยังวัตถุประเภทและขนาดต่าง ๆ ที่ เริ่มต้นที่สถานที่เดียวกัน - แสดงแบบเดียวกัน

ฉันไม่เชื่อว่าฉันเคยพบระบบที่ใช้งานไม่ได้และในทางปฏิบัติคุณอาจปลอดภัย ไม่น้อยก็ผิดและอาจล้มเหลวในบางแพลตฟอร์ม (ตัวอย่างตามสมมุติฐาน: การใช้งาน "การดีบัก" ซึ่งรวมถึงข้อมูลประเภทที่มีตัวชี้ทุกตัวฉันคิดว่าการใช้งาน C ใน Symbolics "Lisp Machines" ทำอะไรเช่นนี้)


5
+1 นอกจากนี้ยังเป็นเรื่องเล็กน้อยที่จะตรวจสอบว่าอาเรย์จะส่งผลให้การ&stringทำงานเหมือนกับstring(แทนที่จะส่งผลให้เกิดความเสียหายหน่วยความจำแบบสุ่มเช่นเดียวกับคำตอบอื่น ๆ ที่ยืนยันผิด):printf("%x\n%x\n", string, &string);
Josh Kelley

@ Josh Kelley ที่น่าสนใจฉันจะเอามันไปแล้วว่าจะไม่เป็นกรณีที่มีตัวชี้ไปยังสตริงที่จัดสรรผ่าน malloc ()?
abeln

4
@abeln: ถูกต้อง สำหรับสตริงที่จัดสรรผ่าน malloc () stringเป็นตัวชี้และ&stringเป็นที่อยู่ของตัวชี้นั้นดังนั้นทั้งสองจึงไม่สามารถใช้แทนกันได้ ขณะที่แกเร็ ธ อธิบายได้สำหรับกรณีของอาร์เรย์ผุstringและ&stringมีเทคนิคไม่ได้ประเภทเดียวกัน (ถึงแม้พวกเขาจะเกิดขึ้นจะใช้แทนกันได้สำหรับสถาปัตยกรรมส่วนใหญ่) และ GCC -Wallจะให้คำเตือนสำหรับตัวอย่างที่สองของคุณถ้าคุณเปิด
Josh Kelley

@Josh Kelley: ขอบคุณฉันดีใจที่ฉันสามารถล้างใจเกี่ยวกับเรื่องนี้
abeln

1
@JCooper str, & str [0] ทั้งคู่แสดงสิ่งเดียวกัน (เริ่มต้นของอาร์เรย์) และแม้ว่าไม่จำเป็นต้อง & str เป็นที่อยู่หน่วยความจำเดียวกัน ตอนนี้ strPtr ชี้ไปที่ str ดังนั้นที่อยู่หน่วยความจำที่เก็บไว้ภายใน strPtr จะเหมือนกับ str และนั่นคือ 12fe60 สุดท้าย & strPtr คือที่อยู่ของตัวแปร strPtr นี่ไม่ใช่ค่าที่เก็บใน strptr แต่เป็นที่อยู่หน่วยความจำจริงของ strPtr เนื่องจาก strptr เป็นตัวแปรที่แตกต่างจากที่อื่น ๆ ทั้งหมดมันจึงมีที่อยู่หน่วยความจำที่แตกต่างกันในกรณีนี้ 12fe54
Juan Besa

-9

ฉันคิดว่าสิ่งนี้ถูกต้องและอาจช่วยได้ อย่าลังเลที่จะแก้ไขหากคุณพบข้อผิดพลาด ฉันใหม่ที่ C.

char str[]  
  1. อาร์เรย์ของค่าประเภทถ่านด้วยที่อยู่ของตัวเองในหน่วยความจำ
  2. อาร์เรย์ของค่าประเภทถ่านที่มีที่อยู่ของตัวเองในหน่วยความจำให้มากที่สุดเท่าที่อยู่ติดต่อกันเป็นองค์ประกอบในอาร์เรย์
  3. รวมทั้งอักขระ null เลิกจ้าง'\0' &str, &str[0]และstrทั้งสามเป็นตัวแทนของตำแหน่งเดียวกันในหน่วยความจำซึ่งเป็นที่อยู่ขององค์ประกอบแรกของอาร์เรย์str

    ถ่าน * strPtr = & str [0]; // การประกาศและการเริ่มต้น

หรือคุณสามารถแยกสิ่งนี้เป็นสองสิ่ง:

char *strPtr; strPtr = &str[0];
  1. strPtr เป็นตัวชี้ไปที่ char
  2. strPtr คะแนนที่อาร์เรย์ str
  3. strPtr เป็นตัวแปรที่มีที่อยู่ของตัวเองในหน่วยความจำ
  4. strPtr เป็นตัวแปรที่เก็บค่าที่อยู่ &str[0]
  5. strPtr ที่อยู่ของตัวเองในหน่วยความจำแตกต่างจากที่อยู่หน่วยความจำที่เก็บ (ที่อยู่ของอาเรย์ในหน่วยความจำ aka & str [0])
  6. &strPtr แสดงถึงที่อยู่ของ strPtr เอง

ฉันคิดว่าคุณสามารถประกาศตัวชี้ไปยังตัวชี้เป็น:

char **vPtr = &strPtr;  

ประกาศและเริ่มต้นด้วยที่อยู่ของตัวชี้ strPtr

อีกวิธีหนึ่งคุณสามารถแยกเป็นสอง:

char **vPtr;
*vPtr = &strPtr
  1. *vPtr ชี้ไปที่ตัวชี้ strPtr
  2. *vPtr เป็นตัวแปรที่มีที่อยู่ของตัวเองในหน่วยความจำ
  3. *vPtr เป็นตัวแปรที่เก็บค่าที่อยู่ & strPtr
  4. ความคิดเห็นสุดท้าย: คุณไม่สามารถทำstr++, strที่อยู่เป็นconstแต่คุณสามารถทำได้strPtr++
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.