Lý do cho tiêu chuẩn C để xem xét đệ quy là gì?


9

Tiêu chuẩn C99 cho biết trong 6.5.16: 2:

Một toán tử gán sẽ có một giá trị có thể sửa đổi là toán hạng bên trái của nó.

và trong 6.3.2.1:1:

Một giá trị có thể sửa đổi là một giá trị không có kiểu mảng, không có kiểu không hoàn chỉnh, không có kiểu đủ điều kiện và nếu nó là cấu trúc hoặc liên kết, không có bất kỳ thành viên nào (bao gồm, đệ quy, bất kỳ thành viên nào hoặc phần tử của tất cả các tập hợp hoặc kết hợp có chứa với một loại đủ điều kiện const.

Bây giờ, hãy xem xét một không phải const structvới một constlĩnh vực.

typedef struct S_s {
    const int _a;
} S_t;

Theo tiêu chuẩn, mã sau đây là hành vi không xác định (UB):

S_t s1;
S_t s2 = { ._a = 2 };
s1 = s2;

Vấn đề ngữ nghĩa với điều này là thực thể kèm theo ( struct) phải được coi là có thể ghi (không chỉ đọc), được đánh giá theo loại thực thể được khai báo ( S_t s1), nhưng không nên được xem là có thể ghi theo cách diễn đạt của tiêu chuẩn (2 mệnh đề trên đầu trang) vì constlĩnh vực _a. Tiêu chuẩn làm cho không rõ ràng đối với một lập trình viên đọc mã rằng bài tập thực sự là một UB, bởi vì không thể nói rằng đó là định nghĩa về struct S_s ... S_tkiểu.

Hơn nữa, quyền truy cập chỉ đọc vào trường chỉ được thực thi theo cú pháp. Không có cách nào một số constlĩnh vực không const structthực sự sẽ được đặt vào bộ lưu trữ chỉ đọc. Nhưng cách diễn đạt tiêu chuẩn đó vượt quá mã mà cố tình bỏ đi constvòng loại của các trường trong thủ tục truy cập của các trường này, như vậy ( Có phải là một ý tưởng tốt để cấu thành các trường cấu trúc trong C? ):

(*)

#include <stdlib.h>
#include <stdio.h>

typedef struct S_s {
    const int _a;
} S_t;

S_t *
create_S(void) {
    return calloc(sizeof(S_t), 1);
}

void
destroy_S(S_t *s) {
    free(s);
}

const int
get_S_a(const S_t *s) {
    return s->_a;
}

void
set_S_a(S_t *s, const int a) {
    int *a_p = (int *)&s->_a;
    *a_p = a;
}

int
main(void) {
    S_t s1;
    // s1._a = 5; // Error
    set_S_a(&s1, 5); // OK
    S_t *s2 = create_S();
    // s2->_a = 8; // Error
    set_S_a(s2, 8); // OK

    printf("s1.a == %d\n", get_S_a(&s1));
    printf("s2->a == %d\n", get_S_a(s2));

    destroy_S(s2);
}

Vì vậy, vì một số lý do, toàn bộ structchỉ đọc là đủ để khai báo nóconst

const S_t s3;

Nhưng đối với toàn bộ structlà không chỉ đọc thì không đủ để khai báo nó const.

Những gì tôi nghĩ sẽ tốt hơn, là:

  1. Để hạn chế việc tạo ra các constcấu trúc phi consttrường với các trường và đưa ra chẩn đoán trong trường hợp đó. Điều đó sẽ làm rõ rằng các structtrường chỉ đọc có chứa chính nó chỉ đọc.
  2. Để xác định hành vi trong trường hợp ghi vào consttrường không thuộc constcấu trúc để làm cho mã ở trên (*) tuân thủ Tiêu chuẩn.

Nếu không thì hành vi không nhất quán và khó hiểu.

Vậy, lý do nào để C Standard cân nhắc - tính constđệ quy, như nó đặt ra?


Thành thật mà nói, tôi không thấy một câu hỏi trong đó.
Bart van Ingen Schenau 30/12/13

@BartvanIngenSchenau đã chỉnh sửa để thêm câu hỏi được nêu trong chủ đề vào cuối cơ thể
Michael Pankov

1
Tại sao các downvote?
Michael Pankov

Câu trả lời:


4

Vì vậy, lý do nào để C Standard xem xét đệ quy, vì nó đặt ra?

Từ quan điểm loại một mình, không làm như vậy sẽ không có cơ sở (nói cách khác: bị phá vỡ khủng khiếp và cố ý không đáng tin cậy).

Và đó là vì ý nghĩa của "=" trên một cấu trúc: đó là một phép gán đệ quy. Theo sau đó cuối cùng bạn có một s1._a = <value>"bên trong các quy tắc gõ" xảy ra. Nếu tiêu chuẩn cho phép điều này đối với các consttrường "lồng nhau" , thì việc thêm một sự không nhất quán nghiêm trọng trong định nghĩa hệ thống loại của nó là một mâu thuẫn rõ ràng (cũng có thể constloại bỏ tính năng này, vì nó trở nên vô dụng và không đáng tin theo định nghĩa của nó).

Giải pháp của bạn (1), theo như tôi hiểu, là không cần thiết buộc toàn bộ cấu trúc phải ở constbất cứ khi nào một trong các trường của nó const. Theo cách này, s1._b = bsẽ là bất hợp pháp đối với một trường không phải là const ._btrên một non-const s1có chứa a const a.


Tốt. Chầu như không có một hệ thống âm thanh (giống như một loạt các trường hợp góc được gắn vào nhau trong suốt nhiều năm). Bên cạnh đó, cách khác để đặt sự phân công cho a structmemcpy(s_dest, s_src, sizeof(S_t)). Và tôi khá chắc chắn rằng đó là cách thực tế nó được thực hiện. Và trong trường hợp như vậy, ngay cả "hệ thống loại" hiện tại cũng không cấm bạn làm điều đó.
Michael Pankov

2
Rất đúng. Tôi hy vọng tôi đã không ngụ ý rằng hệ thống loại C là âm thanh, chỉ có điều đó cố tình làm cho một ngữ nghĩa cụ thể không rõ ràng cố tình đánh bại nó. Hơn nữa, mặc dù hệ thống loại C không được thi hành mạnh mẽ, các cách để vi phạm nó thường rõ ràng (con trỏ, truy cập gián tiếp, phôi) - mặc dù các hiệu ứng của nó thường ẩn và khó theo dõi. Do đó, việc có "hàng rào" rõ ràng để vi phạm chúng sẽ thông báo tốt hơn là có mâu thuẫn trong chính các định nghĩa.
Thiago Silva

2

Lý do là các trường chỉ đọc là chỉ đọc. Không có bất ngờ lớn ở đó.

Bạn đang lầm tưởng rằng hiệu ứng duy nhất là về vị trí trong ROM, điều này thực sự là không thể khi có các trường không phải liền kề. Trong thực tế, các trình tối ưu hóa có thể cho rằng các constbiểu thức không được ghi vào và tối ưu hóa dựa trên điều đó. Tất nhiên, giả định đó không tồn tại khi các bí danh không tồn tại.

Giải pháp của bạn (1) phá vỡ mã hợp pháp và hợp lý hiện có. Điều đó sẽ không xảy ra. Giải pháp của bạn (2) khá nhiều loại bỏ ý nghĩa của constcác thành viên. Trong khi điều này sẽ không phá vỡ mã hiện có, nó dường như thiếu một lý do.


Tôi chắc chắn 90% trình tối ưu hóa có thể không cho rằng constcác trường không được ghi vào, bởi vì bạn luôn có thể sử dụng memsethoặc memcpy, và điều đó thậm chí sẽ tuân thủ Tiêu chuẩn. (1) có thể được thực hiện vì, ít nhất, cảnh báo bổ sung, được bật bằng cờ. Lý do của (2) là, chính xác - không có cách nào một thành phần structcó thể được coi là không thể ghi được khi toàn bộ cấu trúc thể ghi được.
Michael Pankov

"Chẩn đoán tùy chọn được xác định bởi cờ" sẽ là một yêu cầu duy nhất cho Tiêu chuẩn yêu cầu. Bên cạnh đó, việc đặt cờ vẫn sẽ phá vỡ mã hiện có, do đó, thực tế không ai bận tâm đến cờ và đó sẽ là một ngõ cụt. Đối với (2), 6.3.2.1:1 chỉ định chính xác điều ngược lại: toàn bộ cấu trúc không thể ghi được mỗi khi có một thành phần. Tuy nhiên, các thành phần khác vẫn có thể được ghi. Cf. C ++ cũng định nghĩa operator=theo các thành viên và do đó không định nghĩa operator=khi nào có một thành viên const. C và C ++ vẫn tương thích ở đây.
MSalters

@constantius - Việc bạn CÓ THỂ làm gì đó để cố tình lảng vảng xung quanh thành viên KHÔNG phải là lý do để trình tối ưu hóa bỏ qua sự không ổn định đó. Bạn CÓ THỂ bỏ đi sự không ổn định bên trong hàm, cho phép bạn thay đổi công cụ. Nhưng trình tối ưu hóa trong ngữ cảnh cuộc gọi vẫn được phép cho rằng bạn sẽ không. Constness rất hữu ích cho lập trình viên, nhưng nó cũng là một công cụ tuyệt vời cho trình tối ưu hóa trong một số trường hợp.
Michael Kohne

Vậy thì tại sao một cấu trúc không thể ghi được cho phép được ghi đè bằng tức là memcpy? Đối với các lý do khác - ok, đó là di sản, nhưng tại sao nó lại được thực hiện theo cách như vậy ngay từ đầu?
Michael Pankov

1
Tôi vẫn tự hỏi nếu nhận xét của bạn memcpylà đúng. AFACIT Trích dẫn của John Bode trong câu hỏi khác của bạn là đúng: mã của bạn ghi vào một đối tượng đủ điều kiện và do đó KHÔNG phải là khiếu nại tiêu chuẩn, kết thúc cuộc thảo luận.
MSalters
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.