chuyển đổi không dùng nữa từ hằng chuỗi thành 'char *'


16

Lỗi này có nghĩa là gì? Tôi không thể giải quyết nó bằng mọi cách.

cảnh báo: chuyển đổi không dùng từ hằng chuỗi sang 'char *' [-Wwrite-chuỗi]


Câu hỏi này nên có trên StackOverflow, không phải Arduino :)
Vijay Chavda

Câu trả lời:


26

Như tôi sẽ không làm, tôi sẽ cung cấp một chút thông tin kỹ thuật nền tảng cho các vấn đề và lỗi này.

Tôi sẽ kiểm tra bốn cách khác nhau để khởi tạo chuỗi C và xem sự khác biệt giữa chúng là gì. Đây là bốn cách trong câu hỏi:

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

Bây giờ để làm điều này, tôi sẽ muốn thay đổi chữ cái thứ ba "i" thành "o" để biến nó thành "Thos là một số văn bản". Điều đó có thể, trong mọi trường hợp (bạn sẽ nghĩ), có thể đạt được bằng cách:

text[2] = 'o';

Bây giờ chúng ta hãy xem mỗi cách khai báo chuỗi làm gì và text[2] = 'o';câu lệnh đó sẽ ảnh hưởng đến mọi thứ như thế nào .

Cách đầu tiên thường thấy nhất : char *text = "This is some text";. Điều này có nghĩa là gì? Chà, trong C, nó có nghĩa đen là "Tạo một biến được gọi textlà một con trỏ đọc ghi vào chuỗi ký tự này được giữ trong không gian chỉ đọc (mã).". Nếu bạn có tùy chọn -Wwrite-stringsbật thì bạn sẽ nhận được cảnh báo như đã thấy trong câu hỏi trên.

Về cơ bản điều đó có nghĩa là "Cảnh báo: Bạn đã cố gắng tạo một biến là điểm đọc-ghi đến một khu vực bạn không thể ghi vào". Nếu bạn cố gắng và sau đó đặt ký tự thứ ba thành "o" thì thực tế bạn sẽ cố gắng viết vào một khu vực chỉ đọc và mọi thứ sẽ không tốt đẹp. Trên PC truyền thống có Linux dẫn đến:

Phân đoạn lỗi

Bây giờ là cái thứ hai : char text[] = "This is some text";. Theo nghĩa đen, trong C, có nghĩa là "Tạo một mảng kiểu" char "và khởi tạo nó với dữ liệu" Đây là một số văn bản \ 0 ". Kích thước của mảng sẽ đủ lớn để lưu trữ dữ liệu". Vì vậy, thực sự phân bổ RAM và sao chép giá trị "Đây là một số văn bản \ 0" vào nó khi chạy. Không có cảnh báo, không có lỗi, hoàn toàn hợp lệ. Và đúng cách để làm điều đó nếu bạn muốn có thể chỉnh sửa dữ liệu . Hãy thử chạy lệnh text[2] = 'o':

Thos là một số văn bản

Nó đã làm việc, hoàn hảo. Tốt

Bây giờ cách thứ ba : const char *text = "This is some text";. Một lần nữa nghĩa đen: "Tạo một biến gọi là" văn bản "là con trỏ chỉ đọc dữ liệu này trong bộ nhớ chỉ đọc.". Lưu ý rằng cả con trỏ và dữ liệu hiện chỉ đọc. Không có lỗi, không có cảnh báo. Điều gì xảy ra nếu chúng ta thử và chạy lệnh thử nghiệm của chúng tôi? Vâng, chúng tôi không thể. Trình biên dịch bây giờ thông minh và biết rằng chúng tôi đang cố gắng làm điều gì đó xấu:

lỗi: gán vị trí chỉ đọc '* (văn bản + 2u)'

Nó thậm chí sẽ không được biên dịch. Cố gắng ghi vào bộ nhớ chỉ đọc hiện được bảo vệ bởi vì chúng tôi đã nói với trình biên dịch rằng con trỏ của chúng tôi là bộ nhớ chỉ đọc. Tất nhiên, nó không được trỏ đến bộ nhớ chỉ đọc, nhưng nếu bạn trỏ nó để đọc-ghi bộ nhớ (RAM) bộ nhớ sẽ vẫn được bảo vệ khỏi được ghi vào bởi trình biên dịch.

Cuối cùng là hình thức cuối cùng : const char text[] = "This is some text";. Một lần nữa, giống như trước đây, []nó phân bổ một mảng trong RAM và sao chép dữ liệu vào nó. Tuy nhiên, bây giờ đây là một mảng chỉ đọc. Bạn không thể viết thư cho nó vì con trỏ tới nó được gắn thẻ là const. Cố gắng viết cho nó kết quả là:

lỗi: gán vị trí chỉ đọc '* (văn bản + 2u)'

Vì vậy, một bản tóm tắt nhanh về nơi chúng ta đang ở:

Hình thức này là hoàn toàn không hợp lệ và nên tránh bằng mọi giá. Nó mở ra cánh cửa cho tất cả những điều tồi tệ xảy ra:

char *text = "This is some text";

Biểu mẫu này là biểu mẫu phù hợp nếu bạn muốn chỉnh sửa dữ liệu:

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

Biểu mẫu này là biểu mẫu phù hợp nếu bạn muốn các chuỗi không được chỉnh sửa:

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

Hình thức này có vẻ lãng phí RAM nhưng nó có công dụng của nó. Tốt nhất hãy quên nó đi ngay bây giờ.

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

6
Điều đáng chú ý là trên Arduinos (ít nhất là dựa trên AVR), chuỗi ký tự chuỗi sống trong RAM, trừ khi bạn khai báo chúng với macro như PROGMEM, PSTR()hoặc F(). Như vậy, const char text[]không sử dụng nhiều RAM hơn const char *text.
Edgar Bonet

Teensyduino và nhiều công cụ tương thích arduino gần đây khác tự động đặt chuỗi ký tự trong không gian mã, do đó, đáng để kiểm tra xem có cần F () trên bảng của bạn hay không.
Craig.Feied

@ Craig.Feied Nói chung F () nên được sử dụng bất kể. Những người không "cần" nó có xu hướng định nghĩa nó là một (const char *)(...)vai diễn đơn giản . Không có tác dụng thực sự nếu bảng không cần nó, nhưng sẽ tiết kiệm rất nhiều nếu sau đó bạn chuyển mã của mình sang một bảng.
Majenko

5

Để giải thích về câu trả lời xuất sắc của Makenko, có một lý do chính đáng tại sao trình biên dịch cảnh báo bạn về điều này. Hãy làm một bản phác thảo thử nghiệm:

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

Chúng tôi có hai biến ở đây, foo và bar. Tôi sửa đổi một trong những cái trong setup (), nhưng xem kết quả là gì:

Thos is some text
Thos is some text

Cả hai đã thay đổi!

Trong thực tế nếu chúng ta nhìn vào các cảnh báo chúng ta thấy:

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

Trình biên dịch biết điều này là tinh ranh, và nó là đúng! Lý do cho điều này là, trình biên dịch (một cách hợp lý) hy vọng rằng các hằng chuỗi không thay đổi (vì chúng là hằng số). Do đó, nếu bạn tham chiếu chuỗi liên tục "This is some text"nhiều lần trong mã của mình, nó được phép phân bổ cùng một bộ nhớ cho tất cả chúng. Bây giờ nếu bạn sửa đổi một, bạn sửa đổi tất cả chúng!


Khói thánh! Ai có thể biết ... Điều này có còn đúng với các trình biên dịch ArduinoIDE mới nhất không? Tôi chỉ cố gắng nó trên một ESP32 và nó gây ra lặp đi lặp lại GuruMeditation lỗi.
not2qubit

@ not2qubit Tôi mới thử nghiệm trên Arduino 1.8.9 và nó đúng ở đó.
Nick Gammon

Các cảnh báo là có lý do. Lần này tôi nhận được: cảnh báo: ISO C ++ cấm chuyển đổi hằng chuỗi thành 'char ' [-Wwrite-chuỗi] char bar = "Đây là một số văn bản"; - FORBIDS là một từ mạnh mẽ. Vì bạn bị cấm làm điều đó, trình biên dịch có thể tự do tìm kiếm và chia sẻ cùng một chuỗi qua hai biến. Đừng làm những điều bị cấm ! (Ngoài ra, đọc và loại bỏ các cảnh báo). :)
Nick Gammon

Vì vậy, trong trường hợp bạn bắt gặp mã crappy như thế này, và muốn tồn tại qua ngày. Liệu một tuyên bố ban đầu *foo*barsử dụng các "hằng" chuỗi khác nhau , ngăn điều này xảy ra? Ngoài ra, điều này khác với việc không đặt bất kỳ chuỗi nào, như : char *foo;?
not2qubit

1
Các hằng số khác nhau có thể giúp ích, nhưng cá nhân tôi sẽ không đặt bất cứ thứ gì ở đó và đưa dữ liệu vào đó theo cách thông thường sau này (ví dụ: với new, strcpydelete).
Nick Gammon

4

Hoặc ngừng cố gắng truyền một hằng chuỗi trong đó một hàm lấy một char*hoặc thay đổi hàm để nó const char*thay thế.

Chuỗi như "chuỗi ngẫu nhiên" là hằng số.


Là một văn bản như "ký tự ngẫu nhiên" là một char không đổi?
Federico Corazza

1
Chuỗi ký tự là hằng chuỗi.
Ignacio Vazquez-Abrams

3

Thí dụ:

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

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

void loop ()
  {
  }  // end of loop

Cảnh báo:

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

Hàm foomong đợi một char * (do đó nó có thể sửa đổi) nhưng bạn đang truyền một chuỗi ký tự, không nên sửa đổi.

Trình biên dịch đang cảnh báo bạn không làm điều này. Bị phản đối, nó có thể chuyển từ cảnh báo thành lỗi trong phiên bản trình biên dịch trong tương lai.


Giải pháp: Làm cho foo mất một const char *:

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

Tôi không hiểu Bạn có nghĩa là không thể sửa đổi?

Các phiên bản cũ hơn của C (và C ++) cho phép bạn viết mã như ví dụ của tôi ở trên. Bạn có thể tạo một hàm (như foo) in một cái gì đó mà bạn truyền lại cho nó, và sau đó truyền xuống một chuỗi bằng chữ (ví dụ. foo ("Hi there!");)

Tuy nhiên, một hàm lấy char *làm đối số được phép sửa đổi đối số của nó (nghĩa là sửa đổi Hi there!trong trường hợp này).

Bạn có thể đã viết, ví dụ:

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

Thật không may, bằng cách truyền lại một nghĩa đen, bây giờ bạn đã có khả năng sửa đổi nghĩa đen đó thành "Xin chào!" bây giờ là "Tạm biệt" không tốt. Trong thực tế nếu bạn sao chép trong một chuỗi dài hơn, bạn có thể ghi đè lên các biến khác. Hoặc, trên một số triển khai, bạn sẽ bị vi phạm quyền truy cập vì "Xin chào!" có thể đã được đặt trong RAM chỉ đọc (được bảo vệ).

Vì vậy, các trình biên dịch-nhà văn đang dần phản đối việc sử dụng này, do đó, các hàm mà bạn truyền lại theo nghĩa đen, phải khai báo đối số đó là const.


Có vấn đề gì không nếu tôi không sử dụng một con trỏ?
Federico Corazza

Loại vấn đề gì? Cảnh báo cụ thể đó là về việc chuyển đổi một hằng chuỗi thành một con trỏ char *. Bạn có thể xây dựng?
Nick Gammon

@Nick: Ý bạn là gì "(..) bạn đang chuyển một chuỗi ký tự, không nên sửa đổi". Tôi không hiểu Bạn có nghĩa là can notđược sửa đổi?
Mads Skjern

Tôi đã sửa đổi câu trả lời của tôi. Majenko bao gồm hầu hết các điểm này trong câu trả lời của mình.
Nick Gammon

1

Tôi có lỗi biên dịch này:

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

                         ^

Vui lòng thay thế dòng này:
#define TIME_HEADER "T" // Header tag for serial time sync message

với dòng này:
#define TIME_HEADER 'T' // Header tag for serial time sync message

và biên dịch diễn ra tốt đẹp.


3
Thay đổi này thay đổi định nghĩa từ một chuỗi ký tự "T" thành một ký tự đơn với giá trị của mã ASCII cho chữ hoa T.
dlu
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.