กำลังเริ่มต้นอักขระถ่าน [] ด้วยการฝึกตัวอักษรที่ไม่ถูกต้องหรือไม่?


44

ฉันกำลังอ่านหัวข้อเรื่อง"strlen vs sizeof" บน CodeGuruและหนึ่งในคำตอบกล่าวว่า "มันเป็นการปฏิบัติที่ไม่ดี [sic] สำหรับ initialie [sic] charอาร์เรย์ที่มีตัวอักษรสตริง"

นี่เป็นความจริงหรือว่าเป็นเพียงความคิดเห็นของเขา (แม้ว่าจะเป็น "สมาชิกระดับสูง")?


นี่คือคำถามเดิม:

#include <stdio.h>
#include<string.h>
main()
{
    char string[] = "october";
    strcpy(string, "september");

    printf("the size of %s is %d and the length is %d\n\n", string, sizeof(string), strlen(string));
    return 0;
}

ขวา. ขนาดควรเป็นความยาวบวก 1 ใช่ไหม

นี่คือผลลัพธ์

the size of september is 8 and the length is 9

ขนาดควรเป็น 10 อย่างแน่นอน มันเหมือนการคำนวณขนาดของสตริงก่อนที่จะมีการเปลี่ยนแปลงโดย strcpy แต่ความยาวหลังจาก

มีบางอย่างผิดปกติกับไวยากรณ์ของฉันหรืออะไร


นี่คือคำตอบ :

มันเป็นการปฏิบัติที่ไม่ดีเลยที่จะเริ่มต้นชุดอักขระถ่านด้วยตัวอักษรสตริง ดังนั้นทำอย่างใดอย่างหนึ่งต่อไปนี้:

const char string1[] = "october";
char string2[20]; strcpy(string2, "september");

หมายเหตุ "const" ในบรรทัดแรก เป็นไปได้ไหมว่าผู้เขียนสันนิษฐานว่า c ++ แทนที่จะเป็น c? ใน c ++ หมายถึง "การปฏิบัติที่ไม่ดี" เนื่องจากตัวอักษรควรเป็น const และคอมไพเลอร์ c ++ ล่าสุดจะแจ้งเตือน (หรือข้อผิดพลาด) เกี่ยวกับการกำหนด const literal ให้กับอาร์เรย์ที่ไม่ใช่ const
André

@ André C ++ กำหนดตัวอักษรสตริงเป็นอาร์เรย์ const เพราะนั่นเป็นวิธีที่ปลอดภัยเพียงวิธีเดียวในการจัดการกับพวกเขา C นั้นไม่ใช่ปัญหาดังนั้นคุณจึงมีกฎทางสังคมที่บังคับใช้สิ่งที่ปลอดภัย
Caleth

@Caleth ฉันรู้ว่าฉันพยายามที่จะยืนยันว่าผู้เขียนคำตอบกำลังเข้าใกล้ "การปฏิบัติที่ไม่ดี" จากมุมมองของ c ++
André

@ Andréไม่ใช่การฝึกฝนที่ไม่ดีใน C ++ เพราะไม่ใช่การฝึกฝนมันเป็นข้อผิดพลาดประเภทตรง มันควรจะมีข้อผิดพลาดประเภทใน C แต่ก็เป็นไม่ได้ดังนั้นคุณจะต้องมีกฎคู่มือสไตล์บอกคุณ "มันเป็นสิ่งต้องห้าม"
Caleth

คำตอบ:


59

มันเป็นการปฏิบัติที่ไม่ดีเลยที่จะเริ่มต้นชุดอักขระถ่านด้วยตัวอักษรสตริง

ผู้เขียนความคิดเห็นนั้นไม่เคยพิสูจน์ความจริงเลยและฉันพบว่าคำสั่งนั้นทำให้งง

ใน C (และคุณติดแท็กเป็น C) นั่นเป็นวิธีเดียวที่จะเริ่มต้นอาร์เรย์charด้วยค่าสตริง (การเริ่มต้นนั้นแตกต่างจากการกำหนด) คุณสามารถเขียนได้

char string[] = "october";

หรือ

char string[8] = "october";

หรือ

char string[MAX_MONTH_LENGTH] = "october";

ในกรณีแรกขนาดของอาร์เรย์จะถูกนำมาจากขนาดของ initializer สตริงตัวอักษรจะถูกเก็บไว้เป็นอาร์เรย์ของcharด้วยการสิ้นสุด 0 ไบต์ดังนั้นขนาดของอาร์เรย์คือ 8 ('o', 'c', 't', 't', 'o', 'b', 'e', ​​'r', 0) ในสองกรณีที่สองขนาดของอาร์เรย์จะถูกระบุเป็นส่วนหนึ่งของการประกาศ (8 และMAX_MONTH_LENGTHสิ่งที่เกิดขึ้น)

สิ่งที่คุณไม่สามารถทำได้คือเขียนสิ่งที่ชอบ

char string[];
string = "october";

หรือ

char string[8];
string = "october";

ฯลฯ ในกรณีแรกประกาศของstringคือไม่สมบูรณ์เพราะไม่มีขนาดอาร์เรย์ได้รับการระบุและมีการเริ่มต้นที่จะใช้ขนาดจากไม่มี ในทั้งสองกรณีตัวดำเนินการ=จะไม่ทำงานเนื่องจาก a) นิพจน์อาร์เรย์เช่นstringอาจไม่ใช่เป้าหมายของการมอบหมายและ b) ตัว=ดำเนินการไม่ได้ถูกกำหนดให้คัดลอกเนื้อหาของอาร์เรย์หนึ่งไปยังอีกแถวหนึ่ง

ด้วยโทเค็นเดียวกันคุณไม่สามารถเขียนได้

char string[] = foo;

ที่เป็นอาร์เรย์ของผู้อื่นfoo charการเริ่มต้นรูปแบบนี้จะทำงานกับตัวอักษรสตริงเท่านั้น

แก้ไข

ฉันควรแก้ไขสิ่งนี้เพื่อบอกว่าคุณยังสามารถเริ่มต้นอาร์เรย์เพื่อเก็บสตริงด้วย initializer สไตล์อาร์เรย์เช่น

char string[] = {'o', 'c', 't', 'o', 'b', 'e', 'r', 0};

หรือ

char string[] = {111, 99, 116, 111, 98, 101, 114, 0}; // assumes ASCII

แต่จะเป็นการง่ายกว่าที่จะใช้ตัวอักษรสตริง

แก้ไข2

ในการกำหนดเนื้อหาของอาร์เรย์นอกการประกาศคุณจะต้องใช้strcpy/strncpy(สำหรับสตริงที่สิ้นสุดด้วย 0) หรือmemcpy(สำหรับอาร์เรย์ประเภทอื่น ๆ ):

if (sizeof string > strlen("october"))
  strcpy(string, "october");

หรือ

strncpy(string, "october", sizeof string); // only copies as many characters as will
                                           // fit in the target buffer; 0 terminator
                                           // may not be copied, but the buffer is
                                           // uselessly completely zeroed if the
                                           // string is shorter!


@ KeithThompson: ไม่เห็นด้วยแค่เพิ่มมันเพื่อความสมบูรณ์
John Bode

16
โปรดทราบว่าchar[8] str = "october";การปฏิบัติที่ไม่ดี ฉันต้องนับถ่านด้วยตัวเองเพื่อให้แน่ใจว่าไม่ได้ล้นและอยู่ระหว่างการบำรุงรักษา ... เช่นการแก้ไขข้อผิดพลาดในการสะกดคำจากseprateถึงseparateจะแตกถ้าขนาดไม่อัปเดต
djechlin

1
ฉันเห็นด้วยกับ djechlin มันเป็นการปฏิบัติที่ไม่ดีด้วยเหตุผลที่ได้รับ คำตอบของ JohnBode ไม่ได้แสดงความคิดเห็นเลยในแง่มุม "การปฏิบัติที่ไม่ดี" (ซึ่งเป็นส่วนหลักของคำถาม !!) เพียงแค่อธิบายสิ่งที่คุณสามารถทำได้หรือไม่สามารถเริ่มต้นอาร์เรย์ได้
mastov

ผู้เยาว์: เนื่องจากค่า 'ความยาว' ที่ส่งคืนจากstrlen()ไม่มีอักขระ null ใช้MAX_MONTH_LENGTHเพื่อเก็บขนาดสูงสุดที่จำเป็นสำหรับchar string[]มักจะดูผิด IMO MAX_MONTH_SIZEจะดีกว่าที่นี่
chux - Reinstate Monica

10

ปัญหาเดียวที่ฉันจำได้คือการกำหนดสตริงตัวอักษรให้กับchar *:

char var1[] = "september";
var1[0] = 'S'; // Ok - 10 element char array allocated on stack
char const *var2 = "september";
var2[0] = 'S'; // Compile time error - pointer to constant string
char *var3 = "september";
var3[0] = 'S'; // Modifying some memory - which may result in modifying... something or crash

ตัวอย่างเช่นใช้โปรแกรมนี้:

#include <stdio.h>

int main() {
  char *var1 = "september";
  char *var2 = "september";
  var1[0] = 'S';
  printf("%s\n", var2);
}

สิ่งนี้บนแพลตฟอร์มของฉัน (Linux) ขัดข้องขณะพยายามเขียนไปยังหน้าที่ทำเครื่องหมายว่าอ่านอย่างเดียว บนแพลตฟอร์มอื่น ๆ อาจพิมพ์ 'กันยายน' เป็นต้น

ที่กล่าวว่า - การเริ่มต้นตามตัวอักษรทำให้จำนวนการจองที่เฉพาะเจาะจงดังนั้นสิ่งนี้จะไม่ทำงาน:

char buf[] = "May";
strncpy(buf, "September", sizeof(buf)); // Result "Sep"

แต่สิ่งนี้จะ

char buf[32] = "May";
strncpy(buf, "September", sizeof(buf));

ตามคำพูดสุดท้าย - ฉันจะไม่ใช้strcpyเลย:

char buf[8];
strcpy(buf, "very long string very long string"); // Oops. We overwrite some random memory

ในขณะที่คอมไพเลอร์บางตัวสามารถเปลี่ยนเป็นการเรียกที่ปลอดภัยได้อย่างปลอดภัยstrncpyมากขึ้น:

char buf[1024];
strncpy(buf, something_else, sizeof(buf)); // Copies at most sizeof(buf) chars so there is no possibility of buffer overrun. Please note that sizeof(buf) works for arrays but NOT pointers.
buf[sizeof(buf) - 1] = '\0';

ยังคงมีความเสี่ยงในการบัฟเฟอร์ overrun บนว่าstrncpyเพราะมันไม่ได้ null ยุติสตริงคัดลอกเมื่อความยาวของมีค่ามากกว่าsomething_else sizeof(buf)ฉันมักจะตั้งถ่านตัวสุดท้ายbuf[sizeof(buf)-1] = 0เพื่อป้องกันจากนั้นหรือถ้าbufเป็นศูนย์เริ่มต้นให้ใช้sizeof(buf) - 1เป็นความยาวคัดลอก
syockit

ใช้strlcpyหรือstrcpy_sแม้กระทั่งsnprintfถ้าคุณต้อง
user253751

แก้ไขแล้ว. โชคไม่ดีที่ไม่มีวิธีพกพาง่าย ๆ ในการทำเช่นนี้เว้นแต่คุณจะมีความหรูหราในการทำงานร่วมกับคอมไพเลอร์ใหม่ล่าสุด ( strlcpyและsnprintfไม่สามารถเข้าถึงได้โดยตรงบน MSVC อย่างน้อยคำสั่งซื้อและstrcpy_sไม่ได้อยู่ใน * ระวัง)
Maciej Piechotka

@MaciejPiechotka: ขอบคุณพระเจ้า Unix ปฏิเสธภาคผนวกที่สนับสนุนโดย Microsoft k
Deduplicator

6

สิ่งหนึ่งที่ไม่มีเธรดเกิดขึ้นคือ:

char whopping_great[8192] = "foo";

เมื่อเทียบกับ

char whopping_great[8192];
memcpy(whopping_great, "foo", sizeof("foo"));

อดีตจะทำสิ่งที่ชอบ:

memcpy(whopping_great, "foo", sizeof("foo"));
memset(&whopping_great[sizeof("foo")], 0, sizeof(whopping_great)-sizeof("foo"));

หลังเท่านั้น memcpy มาตรฐาน C ยืนยันว่าหากส่วนใดส่วนหนึ่งของอาร์เรย์ถูกเตรียมใช้งานมันคือ ดังนั้นในกรณีนี้ควรทำด้วยตัวเอง ฉันคิดว่านั่นอาจเป็นสิ่งที่ทริชได้มา

ได้อย่างแน่นอน

char whopping_big[8192];
whopping_big[0] = 0;

ดีกว่าอย่างใดอย่างหนึ่ง:

char whopping_big[8192] = {0};

หรือ

char whopping_big[8192] = "";

ps สำหรับคะแนนโบนัสคุณสามารถทำได้:

memcpy(whopping_great, "foo", (1/(sizeof("foo") <= sizeof(whopping_great)))*sizeof("foo"));

เพื่อทิ้งเวลารวบรวมโดยไม่มีข้อผิดพลาดหากคุณกำลังจะล้นอาร์เรย์


5

หลักเพราะคุณจะไม่มีขนาดของchar[]ในตัวแปร / สร้างที่คุณสามารถใช้ภายในโปรแกรม

ตัวอย่างโค้ดจากลิงค์:

 char string[] = "october";
 strcpy(string, "september");

stringถูกจัดสรรบนสแต็กโดยมีความยาว 7 หรือ 8 อักขระ ฉันจำไม่ได้ว่าเป็นโมฆะด้วยวิธีนี้หรือไม่ - เธรดที่คุณเชื่อมโยงเพื่อระบุว่าเป็น

การคัดลอก "กันยายน" บนสตริงนั้นเป็นหน่วยความจำที่เห็นได้ชัดเกิน

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

คุณควรจัดสรรสตริงที่มีขนาดคงที่ (ดีกว่ากำหนดเป็นค่าคงที่) จากนั้นส่งอาร์เรย์และขนาดคงที่ไปยังฟังก์ชันอื่น @John Bode แสดงความคิดเห็นถูกต้องและมีวิธีการลดความเสี่ยงเหล่านี้ พวกเขายังต้องใช้ความพยายามมากขึ้นในส่วนของคุณเพื่อใช้พวกเขา

จากประสบการณ์ของฉันค่าที่ฉันได้รับการเริ่มต้นchar[]มักจะเล็กเกินไปสำหรับค่าอื่น ๆ ที่ฉันต้องมีใน การใช้ค่าคงที่ที่กำหนดช่วยหลีกเลี่ยงปัญหานั้น


sizeof stringจะให้ขนาดของบัฟเฟอร์ (8 ไบต์) ใช้ผลลัพธ์ของนิพจน์นั้นแทนstrlenเมื่อคุณกังวลเรื่องความจำ
ในทำนองเดียวกันคุณสามารถทำให้การตรวจสอบก่อนที่จะเรียกร้องให้เพื่อดูว่าบัฟเฟอร์เป้าหมายของคุณมากพอสำหรับสตริงแหล่งที่มา:strcpy ใช่ถ้าคุณต้องส่งอาเรย์ไปยังฟังก์ชั่นคุณจะต้องผ่านขนาดจริงเช่นกัน: . - John Bodeif (sizeof target > strlen(src)) { strcpy (target, src); }
foo (array, sizeof array / sizeof *array);


2
sizeof stringจะให้ขนาดของบัฟเฟอร์ (8 ไบต์) ใช้ผลลัพธ์ของนิพจน์นั้นแทนstrlenเมื่อคุณกังวลเรื่องความจำ ในทำนองเดียวกันคุณสามารถทำให้การตรวจสอบก่อนที่จะเรียกร้องให้เพื่อดูว่าบัฟเฟอร์เป้าหมายของคุณมากพอสำหรับสตริงแหล่งที่มา:strcpy if (sizeof target > strlen(src)) { strcpy (target, src); }ใช่ถ้าคุณต้องส่งอาเรย์ไปยังฟังก์ชั่นคุณจะต้องผ่านขนาดจริงเช่นกัน: foo (array, sizeof array / sizeof *array);.
John Bode

1
@ JohnBode - ขอบคุณและนั่นคือจุดที่ดี ฉันได้รวมความคิดเห็นของคุณไว้ในคำตอบแล้ว

1
การอ้างอิงถึงชื่ออาเรย์อย่างแม่นยำมากขึ้นstringส่งผลให้เกิดการแปลงchar*แบบอ้อมโดยชี้ไปที่องค์ประกอบแรกของอาเรย์ สิ่งนี้จะสูญเสียข้อมูลขอบเขตของอาร์เรย์ การเรียกใช้ฟังก์ชันเป็นเพียงหนึ่งในหลาย ๆ บริบทที่เกิดขึ้น char *ptr = string;เป็นอีก แม้string[0]เป็นตัวอย่างของสิ่งนี้ []ประกอบการทำงานในชี้ไม่ได้โดยตรงบนอาร์เรย์ อ่านแนะนำ: มาตรา 6 แห่งcomp.lang.c คำถามที่พบบ่อย
Keith Thompson

ในที่สุดคำตอบที่จริงหมายถึงคำถาม!
mastov

2

ฉันคิดว่าความคิด "การปฏิบัติที่ไม่ดี" มาจากความจริงที่ว่าแบบฟอร์มนี้:

char string[] = "october is a nice month";

ทำให้ strcpy โดยนัยจากซอร์สโค้ดเครื่องไปยังสแต็ก

การจัดการลิงก์ไปยังสตริงนั้นมีประสิทธิภาพมากกว่า ชอบกับ:

char *string = "october is a nice month";

หรือโดยตรง:

strcpy(output, "october is a nice month");

(แต่แน่นอนในรหัสส่วนใหญ่มันอาจไม่สำคัญ)


จะไม่ทำสำเนาเฉพาะเมื่อคุณพยายามแก้ไขหรือไม่ ฉันคิดว่าคอมไพเลอร์จะฉลาดกว่านั้น
Cole Johnson

1
แล้วกรณีอย่างเช่นchar time_buf[] = "00:00";ที่คุณจะทำการแก้ไขบัฟเฟอร์ล่ะ? char *เริ่มต้นได้ที่ตัวอักษรสตริงมีการตั้งค่าไปยังที่อยู่ของไบต์แรกจึงพยายามที่จะปรับเปลี่ยนผลในพฤติกรรมที่ไม่ได้กำหนดเพราะวิธีการในการจัดเก็บข้อมูลที่แท้จริงสตริงที่ไม่เป็นที่รู้จัก (การดำเนินงานที่กำหนดไว้) ในขณะที่การปรับเปลี่ยนไบต์ของที่char[]เป็นเพราะตามกฎหมายอย่างสมบูรณ์ การเตรียมใช้งานการคัดลอกไบต์ไปยังพื้นที่ที่สามารถเขียนได้ที่ปันส่วนบนกองซ้อน ที่จะบอกว่ามัน "มีประสิทธิภาพน้อยลง" หรือ "การปฏิบัติที่ไม่ดี" โดยไม่ต้องพูดถึงความแตกต่างของความchar* vs char[]เข้าใจผิด
Braden สุดยอด

-3

ไม่นานนัก แต่คุณควรหลีกเลี่ยงการเริ่มต้นถ่าน [] ถึง string เพราะ "string" เป็น const char * และคุณกำหนดให้ถ่าน * ดังนั้นหากคุณส่งถ่าน [] ไปยังวิธีการที่เปลี่ยนแปลงข้อมูลคุณสามารถมีพฤติกรรมที่น่าสนใจ

ดังที่ฉันได้กล่าวไว้ว่าฉันผสมถ่าน [] กับถ่าน * นั่นไม่ดีเท่าที่พวกเขาต่างกันเล็กน้อย

ไม่มีอะไรผิดปกติเกี่ยวกับการกำหนดข้อมูลให้กับอาร์เรย์ถ่าน แต่เนื่องจากความตั้งใจในการใช้อาร์เรย์นี้คือการใช้มันเป็น 'string' (char *) จึงเป็นเรื่องง่ายที่จะลืมว่าคุณไม่ควรแก้ไขอาร์เรย์นี้


3
ไม่ถูกต้อง การเริ่มต้นคัดลอกเนื้อหาของสตริงตัวอักษรลงในอาร์เรย์ วัตถุอาร์เรย์ไม่ได้constเว้นแต่คุณจะกำหนดไว้อย่างนั้น (และตัวอักษรสตริงใน C ไม่ได้constแม้ว่าความพยายามในการแก้ไขสตริงตัวอักษรใด ๆ จะมีพฤติกรรมที่ไม่ได้กำหนด) char *s = "literal";จะมีพฤติกรรมที่คุณกำลังพูดถึง; มันเขียนได้ดีกว่าเป็นconst char *s = "literal";
Keith Thompson

ความผิดของฉันฉันผสมถ่าน [] กับถ่าน * แต่ฉันจะไม่แน่ใจเกี่ยวกับการคัดลอกเนื้อหาไปยังอาร์เรย์ ตรวจสอบอย่างรวดเร็วด้วย MS C compilator แสดงว่า 'char c [] = "asdf";' จะสร้าง 'สตริง' ในกลุ่ม const แล้วกำหนดที่อยู่นี้ให้กับตัวแปรอาร์เรย์ นั่นคือเหตุผลที่ฉันพูดเกี่ยวกับการหลีกเลี่ยงการมอบหมายอาร์เรย์ที่ไม่ใช่ const char
Dainius

ฉันไม่เชื่อ ลองใช้โปรแกรมนี้และแจ้งให้เราทราบว่าคุณได้รับผลลัพธ์อะไรบ้าง
Keith Thompson

2
"และโดยทั่วไป" asdf "เป็นค่าคงที่ดังนั้นจึงควรมีการประกาศเป็น const" - เหตุผลเดียวกันจะเรียกร้องให้constบนint n = 42;เพราะ42เป็นค่าคงที่
Keith Thompson

1
ไม่สำคัญว่าคุณกำลังใช้เครื่องจักรอะไร มาตรฐานภาษารับประกันว่าcสามารถแก้ไขได้ มันเป็นการรับประกันที่แข็งแกร่งเหมือนกับที่1 + 1ประเมิน2ไว้ หากโปรแกรมที่ฉันลิงค์ไปด้านบนทำสิ่งอื่นนอกเหนือจากการพิมพ์EFGHแสดงว่ามีการติดตั้ง C ที่ไม่สอดคล้อง
Keith Thompson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.