อักขระอาร์เรย์ควรใช้เป็นสตริงอย่างไร


10

ฉันเข้าใจว่าสตริงใน C เป็นเพียงอาร์เรย์อักขระ ดังนั้นฉันลองรหัสต่อไปนี้ แต่ให้ผลลัพธ์ที่แปลกประหลาดเช่นผลลัพธ์ขยะหรือโปรแกรมขัดข้อง:

#include <stdio.h>

int main (void)
{
  char str [5] = "hello";
  puts(str);
}

ทำไมมันไม่ทำงาน

gcc -std=c17 -pedantic-errors -Wall -Wextraมันรวบรวมหมดจดด้วย


หมายเหตุ:โพสต์นี้มีวัตถุประสงค์เพื่อใช้เป็นคำถามที่พบบ่อยซึ่งเป็นที่ยอมรับสำหรับปัญหาที่เกิดจากความล้มเหลวในการจัดสรรห้องสำหรับเทอร์มินัล NUL เมื่อประกาศสตริง

คำตอบ:


12

สตริง AC เป็นอาร์เรย์ตัวละครที่จบลงด้วยเทอร์มิ null

ตัวละครทุกตัวมีค่าตารางสัญลักษณ์ ตัวสิ้นสุด null เป็นค่าสัญลักษณ์0(ศูนย์) มันถูกใช้เพื่อทำเครื่องหมายจุดสิ้นสุดของสตริง นี่เป็นสิ่งจำเป็นเนื่องจากขนาดของสตริงไม่ได้ถูกเก็บไว้ที่ใด

ดังนั้นทุกครั้งที่คุณจัดสรรห้องสำหรับสตริงคุณต้องมีพื้นที่เพียงพอสำหรับอักขระตัวสิ้นสุดเทอร์มินัล ตัวอย่างของคุณไม่ได้ทำเพียงจัดสรรห้องสำหรับ 5 ตัวอักษร"hello"เท่านั้น รหัสที่ถูกต้องควรเป็น:

char str[6] = "hello";

หรือเทียบเท่าคุณสามารถเขียนรหัสการจัดทำเอกสารด้วยตนเองสำหรับ 5 ตัวอักษรพร้อมกับ 1 null terminator:

char str[5+1] = "hello";

เมื่อจัดสรรหน่วยความจำสำหรับสตริงแบบไดนามิกในรันไทม์คุณยังต้องจัดสรรพื้นที่สำหรับตัวยกเลิกเทอร์มินัลด้วย:

char input[n] = ... ;
...
char* str = malloc(strlen(input) + 1);

หากคุณไม่ต่อท้าย null ที่ท้ายของสตริงฟังก์ชันไลบรารีที่คาดว่าสตริงจะทำงานไม่ถูกต้องและคุณจะได้รับข้อผิดพลาด "พฤติกรรมที่ไม่ได้กำหนด" เช่นเอาต์พุตขยะหรือโปรแกรมขัดข้อง

วิธีที่ใช้กันมากที่สุดในการเขียนอักขระ null Terminator ใน C คือการใช้สิ่งที่เรียกว่า "ลำดับหนีฐานแปด" '\0'มองเช่นนี้ นี่เทียบเท่ากับการเขียน 100% 0แต่\ทำหน้าที่เป็นรหัสการจัดทำเอกสารด้วยตนเองเพื่อระบุว่าศูนย์มีความหมายอย่างชัดเจนว่าเป็นตัวสิ้นสุดเทอร์มินัล รหัสเช่นif(str[i] == '\0')จะตรวจสอบว่าตัวละครที่เฉพาะเจาะจงเป็นโมฆะโมฆะ

โปรดทราบว่าคำศัพท์ null terminator ไม่มีส่วนเกี่ยวข้องกับพอยน์เตอร์พอยน์เตอร์หรือNULLมาโคร! สิ่งนี้อาจทำให้สับสน - ชื่อคล้ายกันมาก แต่มีความหมายแตกต่างกันมาก นี่คือเหตุผลว่าทำไม null terminator บางครั้งถูกอ้างถึงNULด้วยหนึ่ง L เพื่อไม่ให้สับสนกับNULLหรือพอยน์เตอร์พอยน์เตอร์ ดูคำตอบสำหรับคำถาม SO นี้สำหรับรายละเอียดเพิ่มเติม

"hello"ในรหัสของคุณเรียกว่าอักษรสตริง นี่จะถือเป็นสตริงอ่านอย่างเดียว ""ไวยากรณ์หมายความว่าคอมไพเลอร์จะผนวกเทอร์มิโมฆะในตอนท้ายของสตริงตัวอักษรโดยอัตโนมัติ ดังนั้นถ้าคุณพิมพ์ออกมาsizeof("hello")คุณจะได้ 6, ไม่ใช่ 5, เพราะคุณได้ขนาดของอาร์เรย์รวมถึง null terminator


มันรวบรวมอย่างสะอาดด้วย gcc

แน่นอนไม่ได้เตือน นี่เป็นเพราะรายละเอียด / ข้อบกพร่องเล็ก ๆ น้อย ๆ ในภาษา C ที่ช่วยให้อาร์เรย์อักขระสามารถเริ่มต้นด้วยตัวอักษรสตริงที่ประกอบด้วยตัวอักษรให้มากที่สุดเท่าที่มีห้องพักในอาเรย์แล้วละทิ้งเทอร์มิเนเตอร์ null (C17 6.7.9 / 15) ภาษามีพฤติกรรมแบบนี้โดยเจตนาด้วยเหตุผลทางประวัติศาสตร์ดูการวินิจฉัย gcc ที่ไม่สอดคล้องกันสำหรับการเริ่มต้นสตริงสำหรับรายละเอียด โปรดทราบว่า C ++ นั้นแตกต่างกันที่นี่และไม่อนุญาตให้ใช้เคล็ดลับ / ข้อบกพร่องนี้


1
คุณควรพูดถึงchar str[] = "hello";กรณี
Jabberwocky

@Jabberwocky นี่คือ wiki ชุมชนรู้สึกอิสระที่จะแก้ไขและมีส่วนร่วม
Lundin

1
... และอาจเป็นปัญหาchar *str = "hello";... str[0] = foo;ด้วย
Jabberwocky

อาจขยายความหมายของการใช้sizeofเพื่อการใช้งานกับพารามิเตอร์ฟังก์ชันโดยเฉพาะอย่างยิ่งเมื่อกำหนดเป็นอาร์เรย์
ใบพัดสภาพอากาศ

@WeatherVane ควรได้รับการคุ้มครองโดยคำถามที่พบบ่อยอื่นที่นี่: stackoverflow.com/questions/492384/…
Lundin

4

จากมาตรฐาน C (7.1.1 คำจำกัดความของคำศัพท์)

1 สตริงเป็นลำดับตัวอักษรที่ต่อเนื่องกันซึ่งถูกยกเลิกและรวมถึงอักขระ null ตัวแรก บางครั้งคำว่าสตริงมัลติไบต์จะใช้แทนเพื่อเน้นการประมวลผลพิเศษที่กำหนดให้กับอักขระหลายไบต์ที่มีอยู่ในสตริงหรือเพื่อหลีกเลี่ยงความสับสนกับสตริงที่กว้าง ตัวชี้ไปยังสตริงเป็นตัวชี้ไปยังอักขระเริ่มต้น (ต่ำสุดที่ระบุ) ความยาวของสตริงคือจำนวนไบต์ก่อนหน้าอักขระ null และค่าของสตริงคือลำดับของค่าของอักขระที่มีอยู่ตามลำดับ

ในการประกาศนี้

char str [5] = "hello";

สตริงตัวอักษร"hello"มีการแสดงภายในเช่น

{ 'h', 'e', 'l', 'l', 'o', '\0' }

ดังนั้นจึงมี 6 ตัวอักษรรวมถึงศูนย์ยุติ องค์ประกอบของมันจะใช้ในการเริ่มต้นอาร์เรย์ตัวละครstrที่สำรองพื้นที่เพียง 5 ตัวอักษร

มาตรฐาน C (ตรงข้ามกับมาตรฐาน C ++) อนุญาตให้มีการเริ่มต้นของอาเรย์ตัวอักษรเมื่อไม่ได้ใช้ศูนย์ยุติของตัวอักษรสตริงเป็นตัวเริ่มต้น

อย่างไรก็ตามผลลัพธ์อาร์เรย์อักขระstrไม่มีสตริง

หากคุณต้องการให้อาร์เรย์จะมีสตริงที่คุณสามารถเขียน

char str [6] = "hello";

หรือเพียงแค่

char str [] = "hello";

ในกรณีสุดท้ายขนาดของอาร์เรย์อักขระจะถูกกำหนดจากจำนวนของ initializers ของตัวอักษรสตริงที่เท่ากับ 6


0

สตริงทั้งหมดสามารถพิจารณาว่าเป็นอาร์เรย์ของอักขระ ( ใช่ ) ได้หรือไม่อาร์เรย์อักขระทั้งหมดสามารถถือว่าเป็นสตริง ( ไม่ )

ทำไมจะไม่ล่ะ? และทำไมมันสำคัญ?

นอกเหนือจากคำตอบอื่น ๆ ที่อธิบายว่าความยาวของสตริงไม่ได้ถูกเก็บไว้ที่ใดก็ได้ซึ่งเป็นส่วนหนึ่งของสตริงและการอ้างอิงถึงมาตรฐานที่สตริงถูกกำหนดไว้ฟลิปด้านคือ "ฟังก์ชัน C ไลบรารีจัดการกับสตริงได้อย่างไร"

ในขณะที่อาเรย์ตัวละครสามารถเก็บอักขระตัวเดียวกันได้ แต่เป็นอาเรย์ของตัวละครเว้นแต่ว่าตัวอักษรตัวสุดท้ายจะตามด้วยอักขระที่ยกเลิก นั่นNUL-ยุติตัวละครเป็นสิ่งที่ช่วยให้อาร์เรย์ของตัวอักษรที่จะได้รับการพิจารณา (จัดการเป็น) สตริง

ฟังก์ชั่นทั้งหมดในซีที่คาดว่าสตริงเป็นอาร์กิวเมนต์ลำดับคาดว่าของตัวละครที่จะNUL สิ้นสุด ทำไม?

มันเกี่ยวข้องกับการทำงานของฟังก์ชั่นสตริงทั้งหมด เนื่องจากความยาวไม่ได้รวมอยู่ในอาเรย์, ฟังก์ชั่นสตริง, สแกนไปข้างหน้าในอาเรย์จนกระทั่งพบตัวอักษร nul (เช่น'\0'- เทียบเท่าทศนิยม0) ดูASCII โต๊ะและคำอธิบาย โดยไม่คำนึงถึงว่าคุณกำลังใช้strcpy, strchr, strcspnฯลฯ .. ฟังก์ชันสตริงทั้งหมดพึ่งพาNUL-ยุติตัวละครที่ถูกนำเสนอในการกำหนดที่ปลายสายว่าเป็น

การเปรียบเทียบของสองฟังก์ชันที่คล้ายกันจากstring.hจะเน้นถึงความสำคัญของอักขระที่ไม่สิ้นสุด ยกตัวอย่างเช่น

    char *strcpy(char *dest, const char *src);

strcpyฟังก์ชั่นเพียงสำเนาไบต์จากsrcไปdestจนNUL-ยุติตัวอักษรที่พบบอกstrcpyว่าจะหยุดการคัดลอกตัวอักษร ตอนนี้ใช้ฟังก์ชั่นที่คล้ายกันmemcpy:

    void *memcpy(void *dest, const void *src, size_t n);

ฟังก์ชั่นทำการดำเนินการที่คล้ายกัน แต่ไม่ได้พิจารณาหรือต้องการให้srcพารามิเตอร์เป็นสตริง เนื่องจากmemcpyไม่สามารถเพียงแค่สแกนไปข้างหน้าในsrcการคัดลอกไบต์ไปdestจนถึงอักขระnul-terminatingจึงต้องมีจำนวนไบต์ที่ชัดเจนเพื่อคัดลอกเป็นพารามิเตอร์ที่สาม พารามิเตอร์ที่สามนี้มีmemcpyข้อมูลขนาดเดียวกันstrcpyสามารถรับได้โดยการสแกนไปข้างหน้าจนกว่าจะพบอักขระที่สิ้นสุด

(ซึ่งเน้นถึงสิ่งที่ผิดพลาดในstrcpy(หรือฟังก์ชั่นที่คาดว่าจะเป็นสตริง) หากคุณล้มเหลวในการจัดหาฟังก์ชั่นด้วยสตริงที่สิ้นสุดด้วยnul - มันไม่มีความคิดว่าจะหยุดที่ไหนและจะแข่งกันอย่างมีความสุข การเรียกใช้พฤติกรรมที่ไม่ได้กำหนดจนกระทั่งอักขระ nulเกิดขึ้นที่ใดที่หนึ่งในหน่วยความจำ - หรือมีการแบ่งกลุ่มความผิดพลาดเกิดขึ้น)

นั่นคือเหตุผลที่ฟังก์ชั่นคาดหวังว่าNUL สิ้นสุดสตริงจะต้องผ่านNUL สิ้นสุดสตริงและทำไมมันเรื่อง


0

สังหรณ์ใจ ...

คิดว่าอาร์เรย์เป็นตัวแปร (เก็บของ) และสตริงเป็นค่า (สามารถวางในตัวแปร)

พวกเขาไม่เหมือนกันอย่างแน่นอน ในกรณีของคุณตัวแปรมีขนาดเล็กเกินไปที่จะเก็บสตริงดังนั้นสตริงจึงถูกตัดออก ("สตริงที่ยกมา" ใน C มีอักขระ null โดยนัยในตอนท้าย)

อย่างไรก็ตามมันเป็นไปได้ที่จะเก็บสตริงในอาร์เรย์ที่มีขนาดใหญ่กว่าสตริง

โปรดทราบว่าตัวดำเนินการกำหนดและเปรียบเทียบ ( = == <ฯลฯ ) ตามปกติจะไม่ทำงานอย่างที่คุณคาดไว้ แต่strxyzตระกูลของฟังก์ชั่นเข้ามาใกล้เมื่อคุณรู้ว่าคุณกำลังทำอะไรอยู่ ดูC คำถามที่พบบ่อยเกี่ยวกับสตริงและอาร์เรย์

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