2010-09-14 36 views
14

Tôi thấy rằng trong đoạn mã sau đoạn mãthay đổi giá trị const trong C

const int i = 2; 
const int* ptr1= &i; 
int* ptr2 = (int*)ptr1; 
*ptr2 =3; 

i 's thay đổi giá trị cho 3. Những gì tôi có thể muốn biết là tại sao được phép này. Các tình huống trong đó điều này có thể trở nên hữu ích là gì?

+13

Vì C là hiện thân hoàn hảo của việc gõ yếu. "Các loại chỉ dành cho vui, nếu bạn không thích kiểu nào đó, chỉ cần bỏ nó";) – delnan

+1

Luật Osborn http://www.anvari.org/fortune/Fortune_Cookies/95_osborn-s-law-variables-won- t.html – Jaydee

+2

Bởi vì C & C++ sẽ cho phép bạn làm bất cứ điều gì bạn muốn, ngay cả khi bạn muốn làm điều gì đó thực sự, thực sự ngu ngốc. –

Trả lời

30

Nó được cho phép bởi vì bạn đã vượt quá độ chói của ptr1 bằng cách đúc nó thành một con trỏ không phải là const. Đây là lý do tại sao phôi có thể rất nguy hiểm.

Lưu ý rằng một số trình biên dịch, chẳng hạn như GCC, sẽ không cho phép bạn bỏ đi trạng thái const như thế này.

+0

+1 câu trả lời hay. –

+1

Lưu ý rằng mã này có thể bị lỗi nếu giá trị const được lưu trữ trong bộ nhớ chỉ đọc. –

+1

Gcc của tôi (4.4.3) chấp nhận mã mà không có bất kỳ khiếu nại nào. trình biên dịch của codepad cũng chấp nhận nó (http://codepad.org/V9Oi65Cf) cũng như ideone's (http://ideone.com/tjCq7) – pmg

12

Bạn đã phá vỡ đảm bảo độ ổn định bằng cách chơi thủ thuật con trỏ. Điều này không được đảm bảo để làm việc tất cả các thời gian và có thể gọi gần như bất kỳ hành vi tùy thuộc vào hệ thống/OS/trình biên dịch mà bạn ném nó tại.

Đừng làm điều đó.

Hoặc ít nhất không làm điều đó trừ khi bạn thực sự biết những gì bạn đang làm và thậm chí sau đó hiểu rằng nó không phải là ít nhất là di động.

5

const thực sự có nghĩa là "chỉ đọc".

Như bạn đã phát hiện ra, giá trị của các đối tượng const có thể thay đổi, nhưng bạn phải sử dụng các phương pháp sai lệch để thực hiện. Và trong khi sử dụng các phương pháp này bạn gọi Hành vi không xác định.

+1

@Konrad: Trong cả C và C++, hành vi không xác định là sửa đổi một đối tượng có chứa 'const'-qualification trong định nghĩa của nó. Các đối tượng này có thể nằm trong bộ nhớ chỉ đọc. –

+0

@Bart: bạn có quyền tất nhiên ... bằng cách nào đó tôi đã bỏ qua rằng đây là một định nghĩa biến. –

2

Tính năng này hoạt động vì bạn đã bỏ một cách rõ ràng số const của con trỏ. Trong khi ptr1 là một con trỏ đến một const int, ptr2 là một con trỏ đến một int, do đó, nó là pointee có thể thay đổi.

Có rất ít lý do chính đáng để thực hiện việc này, nhưng bạn có thể tìm kiếm trường hợp tránh trùng lặp mã. Ví dụ:

const char* letter_at(char* input, int position) 
{ 
    ... stuff ... 
    return &found_char; 
} 

char* editable_letter_at(char* input, int position) 
{ 
    return (char*)(letter_at(input, position)); 
} 

(Ví dụ hơi nham nhở từ C++ ví dụ tại khoản 3 của Effective C++ 3)

2

Nếu bạn đang đi để cast đi constness trong một chương trình C++, hãy sử dụng hơn C++ phong cách của đúc:

Nếu bạn gặp vấn đề liên quan đến loại truyền này (bạn sẽ luôn làm), bạn có thể tìm thấy nơi xảy ra nhanh chóng bằng cách tìm kiếm "const_cast" thay vì thử mọi kết hợp dươi mặt trơi. Bên cạnh đó, nó sẽ giúp những người khác của chúng tôi những người có thể hoặc có thể không đến sau khi bạn.

Chỉ có một vài trường hợp mà tôi có thể thấy điều này hữu ích. Phần lớn trong số đó là trường hợp góc. Tôi sẽ tránh điều này bằng mọi giá nếu bạn đang phát triển trong C++.

10

"Được phép" là trái ngược với "bị chặn", nhưng nó cũng ngược lại với "bị cấm". Bạn đã thấy rằng việc sửa đổi đối tượng const của bạn không bị ngăn chặn, nhưng điều đó không chính xác có nghĩa là nó được cho phép.

Sửa đổi đối tượng const không được "cho phép" theo nghĩa là "được phép". Hành vi của chương trình của bạn không được xác định theo tiêu chuẩn (xem 6.7.3/5). Nó chỉ như vậy sẽ xảy ra rằng trên thực hiện của bạn, trên chạy, bạn thấy giá trị 3. Trên thực hiện khác hoặc vào một ngày khác, bạn có thể thấy một kết quả khác nhau.

Tuy nhiên, nó không bị "ngăn chặn", vì với cách thức hoạt động của C, việc phát hiện nó ở thời gian biên dịch là một vấn đề dừng. Phát hiện nó trong thời gian chạy yêu cầu kiểm tra thêm ở tất cả các truy cập bộ nhớ. Tiêu chuẩn được thiết kế không áp đặt nhiều chi phí cho việc triển khai.

Lý do đúc đi const được hỗ trợ ở tất cả, là bởi vì nếu bạn có một con trỏ const đến một đối tượng không const, ngôn ngữ cho phép bạn (trong cả hai giác quan) để sửa đổi đối tượng đó. Để làm như vậy bạn cần phải loại bỏ vòng loại const. Hậu quả của việc này là các lập trình viên cũng có thể loại bỏ các vòng loại const từ các con trỏ tới các đối tượng thực sự là const.

Dưới đây là một (hơi ngớ ngẩn) ví dụ về mã mà loại bỏ khuôn khổ vòng loại const vì lý do đó:

typedef struct { 
    const char *stringdata; 
    int refcount; 
} atom; 

// returns const, because clients aren't allowed to directly modify atoms, 
// just read them 
const atom *getAtom(const char *s) { 
    atom *a = lookup_in_global_collection_of_atoms(s); 
    if (a == 0) { 
     // error-handling omitted 
     atom *a = malloc(sizeof(atom)); 
     a->stringdata = strdup(s); 
     a->refcount = 1; 
     insert_in_global_collection_of_atoms(a); 
    } else { 
     a->refcount++; 
    } 
    return a; 
} 

// takes const, because that's what the client has 
void derefAtom(const atom *a) { 
    atom *tmp = (atom*)a; 
    --(tmp->refcount); 
    if (tmp->refcount == 0) { 
     remove_from_global_collection_of_atoms(a); 
     free(atom->stringdata); 
     free(atom); 
    } 
} 
void refAtom(const atom *a) { 
    ++(((atom*) a)->refcount); 
} 

Đó là ngớ ngẩn vì một thiết kế tốt hơn sẽ được chuyển tiếp-tuyên bố atom, để làm cho con trỏ tới hoàn toàn mờ đục và cung cấp chức năng truy cập chuỗi dữ liệu. Nhưng C không yêu cầu bạn đóng gói mọi thứ, nó cho phép bạn trả về con trỏ tới các kiểu được xác định đầy đủ và nó muốn hỗ trợ kiểu sử dụng const này để trình bày một khung nhìn chỉ đọc của một đối tượng "có thể thay đổi" được.

2

C phôi cho trình biên dịch biết bạn đang làm gì và bạn sẽ đảm bảo rằng tất cả hoạt động cuối cùng. Nếu bạn sử dụng chúng mà không hiểu chính xác những gì bạn đang làm, bạn có thể gặp rắc rối.

Trong trường hợp này, trình biên dịch hoàn toàn nằm trong quyền của mình để đặt i trong bộ nhớ chỉ đọc, để mã này sẽ bị lỗi khi chạy. Cách khác, nó có thể hoạt động như bạn đã thấy. Tiêu chuẩn xác định điều này là hành vi không xác định, vì vậy nghĩa đen bất cứ điều gì có thể xảy ra. C ban đầu được thiết kế để viết Unix vào, và cố tình cung cấp cho lập trình viên rất nhiều quyền tự do trong thao tác dữ liệu, vì trong hệ điều hành viết thường rất hữu ích để viết mã thực hiện cao, thực hiện những điều không an toàn bất kỳ ngữ cảnh nào khác. Trong mã ứng dụng thông thường, việc đúc nên được thực hiện cẩn thận.

Và không sử dụng phôi kiểu C trong C++. C++ có gia đình riêng của mình của phôi dễ dàng tìm kiếm trong mã và thường chỉ định nhiều hơn những gì diễn viên thực sự đang làm. Trong trường hợp cụ thể này, bạn sẽ sử dụng const_cast, hiển thị chính xác những gì bạn đang làm và rất dễ tìm.

2

Vì C biết rằng các lập trình viên luôn biết họ đang làm gì và luôn làm đúng. Bạn thậm chí có thể truyền nó như:

void* ptr2 = (int(*)(void(*)(char*), int[])) ptr1; 
(*(int*)ptr2) = 3; 

và thậm chí sẽ không phàn nàn gì cả.

1

Tuyên bố const int i = 2; có nghĩa là biểu tượng/biến i giữ giá trị 2, và giá trị không thể thay đổi sử dụng i; nhưng không đảm bảo rằng giá trị không thể thay đổi bằng một số phương tiện khác (như trong ví dụ của OP).

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