การแปลงที่เลิกใช้แล้วจากค่าคงที่สตริงเป็น 'char *'


16

ข้อผิดพลาดนี้หมายความว่าอย่างไร ฉันไม่สามารถแก้ปัญหาในทางใดทางหนึ่ง

คำเตือน: การแปลงที่ไม่สนับสนุนจากค่าคงที่สตริงเป็น 'char *' [-Wwrite-strings]


คำถามนี้ควรจะอยู่ใน StackOverflow ไม่ใช่ Arduino :)
Vijay Chavda

คำตอบ:


26

เช่นเดียวกับที่ฉันเคยชินฉันจะให้ข้อมูลทางเทคนิคเล็กน้อยใน whys และที่ใดก็ตามที่ข้อผิดพลาดนี้

ฉันจะตรวจสอบสี่วิธีที่แตกต่างกันของการเริ่มต้นสตริง C และดูความแตกต่างระหว่างพวกเขา คำถามเหล่านี้มีสี่วิธี:

char *text = "This is some text";
char text[] = "This is some text";
const char *text = "This is some text";
const char text[] = "This is some text";

ตอนนี้ฉันจะต้องการเปลี่ยนอักษรตัวที่สาม "i" เป็น "o" เพื่อให้เป็น "Thos is some text" ในทุกกรณี (คุณอาจคิดว่า) สามารถทำได้โดย:

text[2] = 'o';

ทีนี้ลองมาดูกันว่าแต่ละวิธีในการประกาศสตริงทำอะไรและtext[2] = 'o';คำสั่งนั้นจะมีผลต่อสิ่งต่าง ๆ อย่างไร

วิธีแรกที่เห็นบ่อยที่สุด: char *text = "This is some text";. นี่แปลว่าอะไร? ใน C หมายถึง "สร้างตัวแปรที่เรียกว่าtextซึ่งเป็นตัวชี้อ่าน - เขียนสำหรับตัวอักษรสตริงนี้ซึ่งจัดขึ้นในพื้นที่อ่านอย่างเดียว (รหัส)" หากคุณ-Wwrite-stringsเปิดใช้งานตัวเลือกคุณจะได้รับคำเตือนตามที่เห็นในคำถามข้างต้น

โดยทั่วไปหมายความว่า "คำเตือน: คุณพยายามสร้างตัวแปรที่เป็นจุดอ่าน - เขียนไปยังพื้นที่ที่คุณไม่สามารถเขียนได้" หากคุณลองแล้วตั้งตัวละครที่สามเป็น "o" คุณจะพยายามเขียนไปยังพื้นที่แบบอ่านอย่างเดียวและสิ่งต่าง ๆ จะไม่ดี บนพีซีแบบดั้งเดิมที่มี Linux ซึ่งให้ผลลัพธ์:

การแบ่งส่วนความผิดพลาด

ตอนนี้อันที่สอง: char text[] = "This is some text";. แท้จริงใน C หมายถึง "สร้างอาร์เรย์ประเภท" char "และเริ่มต้นด้วยข้อมูล" นี่คือข้อความ \ 0 "ขนาดของอาร์เรย์จะมีขนาดใหญ่พอที่จะเก็บข้อมูล" เพื่อให้จัดสรร RAM จริงและคัดลอกค่า "นี่คือข้อความ \ 0" ลงไปในตอนรันไทม์ ไม่มีคำเตือนไม่มีข้อผิดพลาดใช้ได้อย่างสมบูรณ์แบบ และวิธีการที่เหมาะสมที่จะทำมันถ้าคุณต้องการเพื่อให้สามารถแก้ไขข้อมูล ลองเรียกใช้คำสั่งtext[2] = 'o':

ข้อความนี้เป็นข้อความบางส่วน

มันทำงานได้อย่างสมบูรณ์แบบ ดี.

ตอนนี้วิธีที่สาม: const char *text = "This is some text";. ความหมายตามตัวอักษรอีกครั้ง: "สร้างตัวแปรที่เรียกว่า" text "ซึ่งเป็นตัวชี้แบบอ่านอย่างเดียวสำหรับข้อมูลนี้ในหน่วยความจำแบบอ่านอย่างเดียว" โปรดทราบว่าขณะนี้ทั้งตัวชี้และข้อมูลเป็นแบบอ่านอย่างเดียว ไม่มีข้อผิดพลาดไม่มีคำเตือน จะเกิดอะไรขึ้นถ้าเราลองเรียกใช้คำสั่งทดสอบของเรา เราทำไม่ได้ ผู้เรียบเรียงตอนนี้ฉลาดและรู้ว่าเรากำลังพยายามทำสิ่งที่ไม่ดี:

ข้อผิดพลาด: การกำหนดตำแหน่งอ่านอย่างเดียว '* (ข้อความ + 2u)'

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

ในที่สุดรูปแบบสุดท้าย: const char text[] = "This is some text";. อีกครั้งเช่นเดียวกับก่อนที่[]จะจัดสรรอาร์เรย์ใน RAM และคัดลอกข้อมูลลงไป อย่างไรก็ตามตอนนี้นี่เป็นอาร์เรย์แบบอ่านอย่างเดียว constคุณไม่สามารถเขียนไปเพราะตัวชี้ไปมันจะติดแท็กเป็น ความพยายามที่จะเขียนถึงผลลัพธ์ใน:

ข้อผิดพลาด: การกำหนดตำแหน่งอ่านอย่างเดียว '* (ข้อความ + 2u)'

ดังนั้นข้อมูลสรุปโดยย่อเกี่ยวกับตำแหน่งที่เราอยู่:

แบบฟอร์มนี้ไม่ถูกต้องสมบูรณ์และควรหลีกเลี่ยงค่าใช้จ่ายทั้งหมด มันเปิดประตูสู่สิ่งเลวร้ายทุกประเภทที่เกิดขึ้น:

char *text = "This is some text";

แบบฟอร์มนี้เป็นแบบฟอร์มที่ถูกต้องหากคุณต้องการให้ข้อมูลแก้ไขได้:

char text[] = "This is some text";

แบบฟอร์มนี้เป็นแบบฟอร์มที่ถูกต้องหากคุณต้องการสตริงที่จะไม่ถูกแก้ไข:

const char *text = "This is some text";

ดูเหมือนว่ารูปแบบนี้จะสิ้นเปลืองแรม แต่มันก็มีประโยชน์ ที่ดีที่สุดลืมมันไปตอนนี้

const char text[] = "This is some text";

6
มันเป็นที่น่าสังเกตว่าใน Arduinos (คน AVR-based อย่างน้อย), อักษรสตริงที่อาศัยอยู่ใน RAM เว้นแต่คุณจะประกาศให้กับแมโครเช่นPROGMEM, หรือPSTR() F()ดังนั้นจึงconst char text[]ไม่ได้ใช้ RAM const char *textมากกว่า
Edgar Bonet

Teensyduino และอื่น ๆ อีกมากมาย arduino-compatibles ที่ผ่านมาวางสตริงตัวอักษรในพื้นที่โค้ดดังนั้นจึงควรตรวจสอบเพื่อดูว่าจำเป็นต้องใช้ F () บนกระดานของคุณหรือไม่
Craig.Feied

@ Craig.Feied โดยทั่วไป F () ควรใช้โดยไม่คำนึงถึง คนที่ไม่ "ต้องการ" มันมักจะนิยามว่าเป็นการ(const char *)(...)คัดเลือกนักแสดงที่เรียบง่าย ไม่มีผลจริงหากบอร์ดไม่ต้องการ แต่เป็นการประหยัดมากถ้าคุณจากนั้นย้ายรหัสของคุณไปยังบอร์ดที่ทำ
Majenko

5

ในการอธิบายอย่างละเอียดเกี่ยวกับคำตอบที่ยอดเยี่ยมของ Makenko มีเหตุผลที่ดีที่ผู้แปลเตือนคุณเกี่ยวกับเรื่องนี้ มาทำสเก็ตช์ทดสอบกัน:

char *foo = "This is some text";
char *bar = "This is some text";

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();
  foo [2] = 'o';     // change foo only
  Serial.println (foo);
  Serial.println (bar);
  }  // end of setup

void loop ()
  {
  }  // end of loop

เรามีตัวแปรสองตัวที่นี่ foo และ bar ฉันแก้ไขหนึ่งในการตั้งค่า () แต่ดูว่าผลลัพธ์คืออะไร:

Thos is some text
Thos is some text

พวกเขาทั้งคู่เปลี่ยนไป!

อันที่จริงถ้าเราดูคำเตือนที่เราเห็น:

sketch_jul14b.ino:1: warning: deprecated conversion from string constant to char*’
sketch_jul14b.ino:2: warning: deprecated conversion from string constant to char*’

คอมไพเลอร์รู้ว่ามันหลบและมันถูกต้อง! เหตุผลสำหรับสิ่งนี้คือคอมไพเลอร์ (สมเหตุสมผล) คาดว่าค่าคงที่สตริงจะไม่เปลี่ยนแปลง (เนื่องจากเป็นค่าคงที่) ดังนั้นหากคุณอ้างถึงค่าคงที่สตริง"This is some text"หลายครั้งในรหัสของคุณจะได้รับอนุญาตให้จัดสรรหน่วยความจำเดียวกันให้กับพวกเขาทั้งหมด ตอนนี้ถ้าคุณปรับเปลี่ยนหนึ่งคุณแก้ไขทั้งหมด!


ควันศักดิ์สิทธิ์! ใครจะรู้บ้าง ... สิ่งนี้ยังคงเป็นจริงสำหรับคอมไพเลอร์ ArduinoIDE ล่าสุดหรือไม่ ฉันเพิ่งลองใช้บน ESP32 และทำให้เกิดข้อผิดพลาดGuruMeditationซ้ำ ๆ
not2qubit

@ not2qubit ฉันเพิ่งทดสอบกับ Arduino 1.8.9 และมันเป็นความจริงที่นั่น
Nick Gammon

คำเตือนอยู่ที่นั่นด้วยเหตุผล เวลานี้ฉันได้รับ: คำเตือน: ISO C ++ ห้ามไม่ให้แปลงค่าคงที่สตริงเป็น 'char ' [-Wwrite-strings] char bar = "นี่คือข้อความบางส่วน"; - FORBIDS เป็นคำที่แข็งแกร่ง เนื่องจากคุณไม่ได้รับอนุญาตให้ทำเช่นนั้นคอมไพเลอร์มีอิสระที่จะโคลนและแบ่งปันสตริงเดียวกันกับตัวแปรสองตัว อย่าทำสิ่งต้องห้าม ! (อ่านและกำจัดคำเตือน) :)
Nick Gammon

ดังนั้นในกรณีที่คุณเจอรหัสเส็งเคร็งเช่นนี้และต้องการที่จะอยู่รอดได้ทั้งวัน การประกาศเริ่มต้น*fooและการ*barใช้สตริง"ค่าคงที่" ที่แตกต่างกันจะป้องกันไม่ให้เกิดขึ้นได้หรือไม่ นอกจากนี้วิธีนี้แตกต่างจากการไม่ใส่สายใด ๆ เช่น: ? char *foo;
not2qubit

1
ค่าคงที่ที่แตกต่างกันอาจช่วย แต่ส่วนตัวผมจะไม่ใส่อะไรที่นั่นและนำข้อมูลที่มีในทางปกติต่อมา (เช่น. ด้วยnew, strcpyและdelete)
Nick Gammon

4

หยุดการพยายามส่งผ่านค่าคงที่สตริงที่ฟังก์ชันใช้char*หรือเปลี่ยนฟังก์ชันเพื่อใช้const char*แทน

สตริงเช่น "สตริงสุ่ม" เป็นค่าคงที่


ข้อความเช่น "อักขระสุ่ม" เป็นอักขระคงที่หรือไม่
Federico Corazza

1
สตริงตัวอักษรเป็นค่าคงที่สตริง
Ignacio Vazquez-Abrams

3

ตัวอย่าง:

void foo (char * s)
  {
  Serial.println (s);
  }

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();
  foo ("bar");
  }  // end of setup

void loop ()
  {
  }  // end of loop

คำเตือน:

sketch_jul14b.ino: In function ‘void setup()’:
sketch_jul14b.ino:10: warning: deprecated conversion from string constant to ‘char*’

ฟังก์ชันfooคาดว่าอักขระ char * (ซึ่งสามารถแก้ไขได้) แต่คุณกำลังส่งผ่านตัวอักษรสตริงซึ่งไม่ควรแก้ไข

คอมไพเลอร์เตือนคุณว่าอย่าทำเช่นนี้ เมื่อเลิกใช้แล้วอาจเปลี่ยนจากคำเตือนเป็นข้อผิดพลาดในเวอร์ชันคอมไพเลอร์ในอนาคต


วิธีแก้ปัญหา: ทำให้ foo ใช้const char *:

void foo (const char * s)
  {
  Serial.println (s);
  }

ฉันไม่เข้าใจ คุณหมายถึงไม่สามารถแก้ไขได้?

เวอร์ชันเก่ากว่าของ C (และ C ++) ให้คุณเขียนโค้ดเหมือนตัวอย่างด้านบน คุณสามารถสร้างฟังก์ชั่น (เช่นfoo) ที่พิมพ์สิ่งที่คุณส่งลงไปจากนั้นส่งผ่านสตริงตัวอักษร (เช่นfoo ("Hi there!");)

อย่างไรก็ตามฟังก์ชันที่ใช้char *เป็นอาร์กิวเมนต์อนุญาตให้แก้ไขอาร์กิวเมนต์ (เช่นแก้ไขHi there!ในกรณีนี้)

คุณอาจจะเขียนเช่น:

void foo (char * s)
  {
  Serial.println (s);
  strcpy (s, "Goodbye");
  }

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

ดังนั้นคอมไพเลอร์นักเขียนจะค่อยๆเลิกconstใช้งานนี้เพื่อให้ฟังก์ชั่นที่คุณผ่านการลงแท้จริงต้องประกาศโต้แย้งว่า


เป็นปัญหาหรือไม่ถ้าฉันไม่ใช้ตัวชี้?
Federico Corazza

ปัญหาประเภทใด คำเตือนนั้นเกี่ยวกับการแปลงค่าคงที่สตริงให้เป็นตัวชี้ char * คุณสามารถทำอย่างละเอียด?
Nick Gammon

@Nick: คุณหมายถึงอะไร "(.. ) คุณกำลังผ่านสตริงตัวอักษรซึ่งไม่ควรแก้ไข" ฉันไม่เข้าใจ คุณหมายถึงcan notถูกปรับเปลี่ยน?
Mads Skjern

ฉันแก้ไขคำตอบของฉัน Majenko กล่าวถึงประเด็นเหล่านี้เป็นส่วนใหญ่ในคำตอบของเขา
Nick Gammon

1

ฉันมีข้อผิดพลาดในการรวบรวม:

TimeSerial.ino:68:29: warning: deprecated conversion from string constant to 'char*' [-Wwrite-strings]
   if(Serial.find(TIME_HEADER)) {

                         ^

โปรดแทนที่บรรทัดนี้:
#define TIME_HEADER "T" // Header tag for serial time sync message

ด้วยสายนี้:
#define TIME_HEADER 'T' // Header tag for serial time sync message

และการรวบรวมเป็นไปด้วยดี


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