Hàm gọi với con trỏ tới non-const và con trỏ tới const đối số của cùng một địa chỉ


14

Tôi muốn viết một hàm nhập một mảng dữ liệu và xuất ra một mảng dữ liệu khác bằng cách sử dụng các con trỏ.

Tôi tự hỏi kết quả là gì nếu cả hai srcvà được dstchỉ đến cùng một địa chỉ vì tôi biết trình biên dịch có thể tối ưu hóa cho const. Là hành vi không xác định? (Tôi đã gắn thẻ cả C và C ++ vì tôi không chắc câu trả lời có thể khác nhau giữa chúng không và tôi muốn biết về cả hai.)

void f(const char *src, char *dst) {
    dst[2] = src[0];
    dst[1] = src[1];
    dst[0] = src[2];
}

int main() {
    char s[] = "123";
    f(s,s);
    printf("%s\n", s);
    return 0;
}

Ngoài câu hỏi trên, điều này có được xác định rõ nếu tôi xóa constmã gốc không?

Câu trả lời:


17

Mặc dù đúng là hành vi được xác định rõ - không đúng khi trình biên dịch có thể "tối ưu hóa cho const" theo nghĩa mà bạn muốn nói.

Nghĩa là, một trình biên dịch không được phép giả định rằng chỉ vì một tham số là a const T* ptr, bộ nhớ được trỏ bởi ptrsẽ không bị thay đổi thông qua một con trỏ khác. Các con trỏ thậm chí không phải bằng nhau. Đây constlà một nghĩa vụ, không phải là sự bảo đảm - nghĩa vụ của bạn (= chức năng) không được thực hiện thay đổi thông qua con trỏ đó.

Để thực sự có sự đảm bảo đó, bạn cần đánh dấu con trỏ bằng restricttừ khóa. Vì vậy, nếu bạn biên dịch hai hàm này:

int foo(const int* x, int* y) {
    int result = *x;
    (*y)++;
    return result + *x;
}

int bar(const int* x, int* restrict y) {
    int result = *x;
    (*y)++;
    return result + *x;
}

các foo()chức năng phải đọc hai lần từ x, trong khi bar()chỉ cần đọc nó một lần:

foo:
        mov     eax, DWORD PTR [rdi]
        add     DWORD PTR [rsi], 1
        add     eax, DWORD PTR [rdi]  # second read
        ret
bar:
        mov     eax, DWORD PTR [rdi]
        add     DWORD PTR [rsi], 1
        add     eax, eax              # no second read
        ret

Xem này trực tiếp trên GodBolt.

restrictchỉ là một từ khóa trong C (kể từ C99); thật không may, cho đến nay nó vẫn chưa được đưa vào C ++ (vì lý do nghèo nàn mà việc giới thiệu nó trong C ++) phức tạp hơn. Nhiều trình biên dịch hỗ trợ nó, tuy nhiên, như __restrict.

Dòng dưới cùng: Trình biên dịch phải hỗ trợ trường hợp sử dụng "bí truyền" của bạn khi biên dịch f()và sẽ không có bất kỳ vấn đề nào với nó.


Xem bài này liên quan đến các trường hợp sử dụng cho restrict.


constkhông phải là một nghĩa vụ của bạn (= hàm) không được thực hiện thay đổi thông qua con trỏ đó. Tiêu chuẩn C cho phép chức năng loại bỏ constthông qua truyền và sau đó sửa đổi đối tượng thông qua kết quả. Về cơ bản, constchỉ là tư vấn và thuận tiện cho lập trình viên để giúp tránh vô tình sửa đổi một đối tượng.
Eric Postpischil

@EricPostpischil: Đó là nghĩa vụ bạn có thể thoát ra.
einpoklum

Một nghĩa vụ bạn có thể thoát ra không phải là một nghĩa vụ.
Eric Postpischil

2
@EricPostpischil: 1. Bạn đang chia tóc ở đây. 2. Điều đó không đúng.
einpoklum

1
Đây là lý do tại sao memcpystrcpyđược khai báo bằng các restrictđối số, trong khi memmovekhông - chỉ có điều sau cho phép trùng lặp giữa các khối bộ nhớ.
Barmar

5

Điều này được xác định rõ (trong C ++, không chắc chắn trong C nữa), có và không có constvòng loại.

Điều đầu tiên cần tìm là quy tắc răng cưa nghiêm ngặt 1 . Nếu srcdsttrỏ đến cùng một đối tượng:

Về constvòng loại, bạn có thể lập luận rằng vì khi dst == srcchức năng của bạn sửa đổi hiệu quả những srcđiểm nào, srckhông nên đủ điều kiện là const. Đây không phải là cách làm constviệc. Hai trường hợp cần được xem xét:

  1. Khi một đối tượng được xác định là const, như trong char const data[42];, sửa đổi nó (trực tiếp hoặc gián tiếp) dẫn đến Hành vi không xác định.
  2. Khi một tham chiếu hoặc con trỏ tới một constđối tượng được xác định, như trong char const* pdata = data;, người ta có thể sửa đổi đối tượng cơ bản với điều kiện nó không được xác định là const2 (xem 1.). Vì vậy, sau đây được xác định rõ:
int main()
{
    int result = 42;
    int const* presult = &result;
    *const_cast<int*>(presult) = 0;
    return *presult; // 0
}

1) Quy tắc răng cưa nghiêm ngặt là gì?
2) const_castan toàn không?


Có lẽ OP có nghĩa là sắp xếp lại các bài tập?
Igor R.

char*char const*không tương thích. _Generic((char *) 0, const char *: 1, default: 0))đánh giá bằng không.
Eric Postpischil

Các cụm từ cụm từ Khi một tham chiếu hoặc một con trỏ tới một constđối tượng được xác định là không chính xác. Bạn có nghĩa là khi một tham chiếu hoặc con trỏ đến một loạiconst đủ tiêu chuẩn được xác định, điều đó không có nghĩa là đối tượng mà nó được đặt thành điểm có thể không được sửa đổi (bằng nhiều cách khác nhau). (Nếu con trỏ trỏ đến một đối tượng, điều đó có nghĩa là đối tượng thực sự theo định nghĩa, do đó hành vi cố gắng sửa đổi nó không được xác định.)constconst
Eric Postpischil

@Eric, tôi chỉ cụ thể khi câu hỏi về Tiêu chuẩn hoặc được gắn thẻ language-lawyer. Chính xác là một giá trị tôi trân trọng, nhưng tôi cũng nhận thấy nó đi kèm với sự phức tạp hơn. Ở đây, tôi quyết định chọn những câu đơn giản và dễ hiểu, bởi vì tôi tin rằng đây là wat OP muốn. Nếu bạn nghĩ khác xin vui lòng trả lời, tôi sẽ là một trong những người đầu tiên nâng cao nó. Dù sao, cảm ơn bạn đã bình luận của bạn.
YSC

3

Điều này được xác định rõ trong C. Quy tắc răng cưa nghiêm ngặt không áp dụng với charloại, cũng như với hai con trỏ cùng loại.

Tôi không chắc ý của bạn là "tối ưu hóa const". Trình biên dịch của tôi (GCC 8.3.0 x86-64) tạo cùng một mã chính xác cho cả hai trường hợp. Nếu bạn thêm trình restrictxác định vào các con trỏ, thì mã được tạo sẽ tốt hơn một chút, nhưng điều đó sẽ không hoạt động đối với trường hợp của bạn, các con trỏ giống nhau.

(C11 §6.5 7)

Một đối tượng sẽ có giá trị được lưu trữ chỉ được truy cập bởi một biểu thức giá trị có một trong các loại sau:
- một loại tương thích với loại hiệu quả của đối tượng,
- một phiên bản đủ điều kiện của loại tương thích với loại đối tượng hiệu quả,
- một loại là loại đã ký hoặc không dấu tương ứng với loại có hiệu lực của đối tượng,
- một loại là loại đã ký hoặc không dấu tương ứng với một phiên bản đủ điều kiện của loại đối tượng có hiệu lực,
- loại tổng hợp hoặc liên kết bao gồm một loại trong số các loại đã nói ở trên giữa các thành viên của nó (bao gồm, đệ quy, một thành viên của một tập hợp phụ hoặc có chứa), hoặc
- một loại ký tự.

Trong trường hợp này (không có restrict), bạn sẽ luôn nhận được 121kết quả.

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.