2008-09-16 103 views
55

Đoạn mã sau (chính xác) đưa ra một cảnh báo trong C và một lỗi trong C++ (sử dụng gcc & g ++ tương ứng, thử nghiệm với phiên bản 3.4.5 và 4.2.1; MSVC dường như không quan tâm):Tại sao tôi không thể chuyển đổi 'char **' thành 'const char * const *' trong C?

char **a; 
const char** b = a; 

Tôi có thể hiểu và chấp nhận điều này.
Giải pháp C++ cho vấn đề này là thay đổi b thành const char * const *, không cho phép gán lại các con trỏ và ngăn bạn phá vỡ tính chính xác của const (C++ FAQ).

char **a; 
const char* const* b = a; 

Tuy nhiên, trong C tinh khiết, phiên bản chỉnh sửa (sử dụng const char * const *) vẫn đưa ra một cảnh báo, và tôi không hiểu tại sao. Có cách nào để giải quyết vấn đề này mà không sử dụng dàn diễn viên không?

Để làm rõ:
1) Tại sao điều này tạo ra cảnh báo trong C? Nó phải hoàn toàn an toàn, và trình biên dịch C++ dường như nhận ra nó như vậy.
2) Cách chính xác để đi về việc chấp nhận char này ** như một tham số trong khi nói (và có trình biên dịch bắt buộc) rằng tôi sẽ không sửa đổi các ký tự nó trỏ đến? Ví dụ, nếu tôi muốn viết một hàm:

void f(const char* const* in) { 
    // Only reads the data from in, does not write to it 
} 

Và tôi muốn gọi nó trên một char **, những gì sẽ là đúng loại cho các tham số?

Chỉnh sửa: Cảm ơn những người đã trả lời, đặc biệt là những người đã giải quyết câu hỏi và/hoặc theo dõi câu trả lời của tôi.

Tôi đã chấp nhận câu trả lời rằng những gì tôi muốn làm không thể được thực hiện mà không có diễn viên, bất kể có nên hay không.

+0

Tôi nhầm lẫn với câu hỏi của bạn, có phải là: "Làm cách nào để C không cảnh báo tôi?" hoặc là nó "Làm thế nào để có được C + + không ném một lỗi biên dịch?" Hoặc có lẽ nó không phải là những thứ này. – user7116

+0

Tôi đồng ý với sáu bản sao, câu hỏi này cần làm rõ thêm. –

+0

Câu hỏi đặt ra là "tại sao tôi không thể chuyển đổi 'char **' thành 'char * const *?" – Kevin

Trả lời

53

Tôi đã gặp vấn đề tương tự này vài năm trước và nó khiến tôi không thể kết thúc.

Quy tắc trong C được nêu rõ hơn (nghĩa là chúng không liệt kê các ngoại lệ như chuyển đổi char** thành const char*const*). Do đó, nó chỉ là không được phép. Với tiêu chuẩn C++, chúng bao gồm nhiều quy tắc hơn để cho phép các trường hợp như thế này.

Cuối cùng, đó chỉ là vấn đề trong tiêu chuẩn C. Tôi hy vọng tiêu chuẩn tiếp theo (hoặc báo cáo kỹ thuật) sẽ giải quyết vấn đề này.

+0

Tại sao lại bỏ phiếu? Vui lòng giải thích! – Kevin

+0

Tôi chắc chắn đã không bỏ phiếu xuống; đó là câu trả lời phù hợp nhất mà tôi đã nhận được cho đến nay. Nếu tôi có 100 điểm danh tiếng thì tôi sẽ bỏ phiếu này. – HappyDude

+1

@Kevin: Nếu bạn vẫn còn xung quanh, tôi tự hỏi làm thế nào chắc chắn bạn là về câu trả lời của bạn rằng nó chỉ là một vấn đề trong tiêu chuẩn - tại thời điểm đó bạn đã thực sự đi qua các tiêu chuẩn để kiểm tra nó? Nếu có, tôi có thể chỉ cần đóng câu hỏi này, vì đây là một câu trả lời như tôi sẽ nhận được. – HappyDude

11

> Tuy nhiên, trong C tinh khiết, đây vẫn đưa ra một cảnh báo, và tôi không hiểu tại sao

Bạn đã xác định vấn đề - mã này không phải là const-chính xác. "Const correct" có nghĩa là, ngoại trừ const_cast và C-style phôi loại bỏ const, bạn không bao giờ có thể sửa đổi một đối tượng const thông qua các con trỏ const hoặc tham chiếu.

Giá trị của const-correctness - const là có, phần lớn, để phát hiện lỗi lập trình viên. Nếu bạn khai báo một cái gì đó như const, bạn nói rằng bạn không nghĩ rằng nó nên được sửa đổi - hoặc ít nhất, những người có quyền truy cập vào phiên bản const chỉ không thể sửa đổi nó. Xem xét:

void foo(const int*); 

Như tuyên bố, foo không có sự cho phép để sửa đổi các số nguyên được trỏ đến bởi đối số của nó.

Nếu bạn không chắc chắn lý do tại sao mã bạn được đăng không phải là const-đúng, hãy xem xét đoạn mã sau, chỉ hơi khác nhau từ mã HappyDude của:

loại
char *y; 

char **a = &y; // a points to y 
const char **b = a; // now b also points to y 

// const protection has been violated, because: 

const char x = 42; // x must never be modified 
*b = &x; // the type of *b is const char *, so set it 
     //  with &x which is const char* .. 
     //  .. so y is set to &x... oops; 
*y = 43; // y == &x... so attempting to modify const 
     //  variable. oops! undefined behavior! 
cout << x << endl; 

Non-const chỉ có thể chuyển đổi sang const các loại theo những cách cụ thể để ngăn chặn bất kỳ hành vi gian lận 'const' nào trên loại dữ liệu mà không có diễn viên rõ ràng.

Đối tượng ban đầu được khai báo const đặc biệt đặc biệt - trình biên dịch có thể giả định rằng chúng không bao giờ thay đổi. Tuy nhiên, nếu 'b' có thể được gán giá trị của 'a' mà không có phép đúc, thì bạn có thể vô tình cố sửa đổi biến const. Điều này sẽ không chỉ phá vỡ kiểm tra mà bạn yêu cầu trình biên dịch thực hiện, để không cho phép bạn thay đổi giá trị biến đó - nó cũng sẽ cho phép bạn phá vỡ tối ưu hóa trình biên dịch!

Trên một số trình biên dịch, thao tác này sẽ in '42', trên một số '43' và các chương trình khác, chương trình sẽ bị lỗi.

Sửa-add:

HappyDude: Cảm nhận của bạn là tại chỗ trên. Hoặc là ngôn ngữ C, hoặc trình biên dịch C bạn đang sử dụng, xử lý const char * const * về cơ bản khác với ngôn ngữ C++ đối xử với nó. Có lẽ xem xét im lặng cảnh báo trình biên dịch cho dòng nguồn này chỉ.

Chỉnh sửa-xóa: loại bỏ typo

+4

Điều bạn nói là đúng, nhưng nó không giải quyết được câu hỏi. Như tôi đã nói, tôi hiểu tại sao chuyển đổi từ char ** thành const char ** là không chính xác. Điều tôi không hiểu là tại sao chuyển đổi từ char ** thành const char * const * không chính xác. Tôi đã cập nhật câu hỏi của mình để làm sáng tỏ. – HappyDude

+3

Thật thú vị khi phản hồi này tiếp tục được bình chọn. Tôi đoán rất nhiều người không đọc qua vài dòng đầu tiên của câu hỏi, một cái gì đó tôi sẽ ghi nhớ cho tương lai. Mặc dù đây là một bài viết khá tốt trên một điểm tương đối mơ hồ, tôi chỉ muốn nó có liên quan: P. – HappyDude

+3

@Aaron: bài đăng của bạn không liên quan gì đến chủ đề, nhưng dù sao cũng được viết. – user7116

0

Tôi không thể nhận được một lỗi khi ngầm đúc char ** để const char * const *, ít nhất là trên MSVC 14 (VS2k5) và g ++ 3.3. 3. GCC 3.3.3 đưa ra một cảnh báo, mà tôi không chắc chắn nếu nó là chính xác trong thực hiện.

test.c:

#include <stdlib.h> 
#include <stdio.h> 
void foo(const char * const * bar) 
{ 
    printf("bar %s null\n", bar ? "is not" : "is"); 
} 

int main(int argc, char **argv) 
{ 
    char **x = NULL; 
    const char* const*y = x; 
    foo(x); 
    foo(y); 
    return 0; 
} 

Output với biên dịch dưới dạng mã C: cl/TC/W4/Wp64 test.c

test.c(8) : warning C4100: 'argv' : unreferenced formal parameter 
test.c(8) : warning C4100: 'argc' : unreferenced formal parameter 

Output với biên dịch như C++: cl/TP/W4/Wp64 test.c

test.c(8) : warning C4100: 'argv' : unreferenced formal parameter 
test.c(8) : warning C4100: 'argc' : unreferenced formal parameter 

Output với gcc: gcc -Wall test.c

012.351.
test2.c: In function `main': 
test2.c:11: warning: initialization from incompatible pointer type 
test2.c:12: warning: passing arg 1 of `foo' from incompatible pointer type 

Output với g ++: g ++ Wall test.c

không có đầu ra

+0

Xin lỗi, tôi nên làm rõ rằng tôi đang biên dịch bằng gcc. Tôi đã cập nhật câu hỏi của mình để làm rõ điều này. May mắn thay (hoặc có lẽ không?) Tôi hiếm khi gặp nhiều cảnh báo khó xử lý trong msvc. – HappyDude

+0

@HappyDude: Tôi đã cập nhật những phát hiện của tôi từ GCC. Và bây giờ tôi thấy những gì bạn đang nói, nhưng tôi không đồng ý với các cảnh báo trình biên dịch chỉ được nêu ra. Tôi sẽ phá vỡ đặc tả C của tôi và xem những gì tôi tìm thấy. – user7116

+0

@sixlettervariables: Cảm ơn bạn đã trả lời! Hãy cho tôi biết nếu bạn tìm thấy một cái gì đó, nhưng tôi nghi ngờ rằng những gì Kevin nói là chính xác và nó không được bao phủ bởi tiêu chuẩn. – HappyDude

0

Tôi khá chắc chắn rằng từ khóa const không bao hàm các dữ liệu không thể thay đổi/là hằng số, chỉ rằng dữ liệu sẽ được coi là chỉ đọc. Hãy xem xét điều này:

const volatile int *const serial_port = SERIAL_PORT; 

đó là mã hợp lệ. Làm thế nào có thể dễ bay hơi và const cùng tồn tại? Đơn giản. volatile cho trình biên dịch luôn luôn đọc bộ nhớ khi sử dụng dữ liệu và const cho trình biên dịch tạo ra một lỗi khi một nỗ lực được thực hiện để ghi vào bộ nhớ bằng cách sử dụng con trỏ serial_port.

Có const giúp trình tối ưu hóa của trình biên dịch không? Không hoàn toàn không. Bởi vì constness có thể được thêm vào và loại bỏ khỏi dữ liệu thông qua quá trình truyền, trình biên dịch không thể tìm ra nếu dữ liệu const thực sự là hằng số (vì phép diễn có thể được thực hiện trong một đơn vị dịch khác).Trong C++, bạn cũng có từ khóa có thể thay đổi để làm phức tạp thêm các vấn đề.

char *const p = (char *) 0xb000; 
//error: p = (char *) 0xc000; 
char **q = (char **)&p; 
*q = (char *)0xc000; // p is now 0xc000 

Điều gì xảy ra khi ghi vào bộ nhớ thực sự chỉ đọc (ROM, ví dụ) có thể không được xác định trong tiêu chuẩn.

+2

Điều này không thực sự giải quyết câu hỏi. Như tôi đã hiểu, loại bỏ const thông qua quá trình đúc là một hoạt động không xác định theo tiêu chuẩn C, mặc dù hầu hết các trình biên dịch sẽ cho phép nó và 'làm điều đúng'. Nhưng nó không phải là những gì tôi hỏi về tất cả. – HappyDude

+2

@HappyDude đúc đi const được xác định rõ. Nó sẽ chỉ không được xác định nếu bạn thực sự cố gắng ghi vào một đối tượng được khai báo là 'const' (hoặc nếu không thì không thể đọc được, chẳng hạn như một chuỗi ký tự). –

9

Để được xem là tương thích, con trỏ nguồn phải được const ở cấp độ gián tiếp phía trước ngay lập tức. Vì vậy, điều này sẽ cung cấp cho bạn các cảnh báo trong GCC:

char **a; 
const char* const* b = a; 

Nhưng điều này sẽ không:

const char **a; 
const char* const* b = a; 

Ngoài ra, bạn có thể bỏ nó:

char **a; 
const char* const* b = (const char **)a; 

Bạn sẽ cần phải cùng cast để gọi hàm f() như bạn đã đề cập. Theo tôi biết, không có cách nào để thực hiện chuyển đổi ngầm trong trường hợp này (ngoại trừ trong C++).

+0

Vui lòng đọc kỹ câu hỏi. Tôi biết rằng đoạn mã đầu tiên không chính xác, như tôi nói trên dòng ngay lập tức sau đoạn mã đó. Điều thứ hai, tuy nhiên, sẽ ổn (theo như tôi có thể biết). Như tôi đã đề cập, MSVC không đưa ra cảnh báo, nhưng gcc thực hiện. – HappyDude

+0

Xin lỗi, tôi đã không giải thích tốt những gì tôi muốn, và cũng không kiểm tra kết quả trong GCC để đảm bảo rằng các mẫu mã là chính xác. Sửa chữa. –

+0

Cảm ơn Fabio, phản hồi đã chỉnh sửa của bạn rõ ràng hơn nhiều. Tôi nghĩ tôi sẽ phải chấp nhận rằng tôi cần phải bỏ nó. – HappyDude

1

Đây là khó chịu, nhưng nếu bạn sẵn sàng để thêm một mức độ chuyển hướng, bạn thường có thể làm như sau để đẩy xuống con trỏ-to-pointer:

char c = 'c'; 
char *p = &c; 
char **a = &p; 

const char *bi = *a; 
const char * const * b = &bi; 

Nó có một chút khác nhau có nghĩa là, nhưng nó thường là hoàn toàn khả thi, và nó không sử dụng dàn diễn viên.

Các vấn đề liên quan