Có an toàn để giữ một con trỏ nằm ngoài giới hạn mà không cần tham chiếu đến nó không? [bản sao]

Jan 10 2021

Có an toàn trong C để giữ một con trỏ nằm ngoài giới hạn (không tham chiếu đến nó) để tính toán thêm không?

void f(int *array)
{
    int *i = array - 1;  // OOB

    while(...) {
        ++i;
        ...
    }
}

void g(int *array, int *end /* past-the-end pointer: OOB */)
{
    while(array != end) {
        ...
        ++array;
    }
}

Tôi tưởng tượng một số trường hợp cực đoan, nếu địa chỉ là địa chỉ đầu tiên của bộ nhớ hoặc địa chỉ cuối cùng ...

Trả lời

9 MikeCAT Jan 10 2021 at 07:31

Cho phép di chuyển con trỏ tới một phần tử qua phần tử cuối cùng, nhưng không cho phép di chuyển xa hơn hoặc di chuyển trước phần tử đầu tiên.

Trích dẫn từ N1570 6.5.6 Các toán tử cộng (điểm 8):

Khi một biểu thức có kiểu số nguyên được thêm vào hoặc trừ khỏi một con trỏ, kết quả có kiểu của toán hạng con trỏ. Nếu toán hạng con trỏ trỏ đến một phần tử của một đối tượng mảng và mảng đủ lớn, thì kết quả trỏ đến một phần tử khác với phần tử ban đầu sao cho hiệu số của các chỉ số con của phần tử mảng kết quả và ban đầu bằng biểu thức số nguyên. Nói cách khác, nếu biểu thức P trỏ đến phần tử thứ i của một đối tượng mảng thì các biểu thức (P) + N (tương đương, N + (P)) và (P) -N (trong đó N có giá trị n) trỏ tương ứng với các phần tử thứ i + n và i-n-thứ của đối tượng mảng, miễn là chúng tồn tại. Hơn nữa, nếu biểu thức P trỏ đến phần tử cuối cùng của một đối tượng mảng, thì biểu thức (P) +1 trỏ một phần qua phần tử cuối cùng của đối tượng mảng và nếu biểu thức Q trỏ một phần qua phần tử cuối cùng của một đối tượng mảng, biểu thức (Q) -1 trỏ đến phần tử cuối cùng của đối tượng mảng. Nếu cả toán hạng con trỏ và kết quả đều trỏ đến các phần tử của cùng một đối tượng mảng hoặc một phần tử vượt qua phần tử cuối cùng của đối tượng mảng, thì phép đánh giá sẽ không tạo ra lỗi tràn; nếu không, hành vi là không xác định. Nếu kết quả trỏ qua phần tử cuối cùng của đối tượng mảng, nó sẽ không được sử dụng làm toán hạng của toán tử một ngôi * được đánh giá.

3 dbush Jan 10 2021 at 07:32

Một con trỏ có thể trỏ đến một phần tử qua phần tử cuối cùng của mảng và phép tính con trỏ có thể được thực hiện giữa con trỏ đó và một con trỏ tới một phần tử của mảng.

Một con trỏ như vậy không thể được tham chiếu đến, nhưng nó có thể được sử dụng trong số học con trỏ. Ví dụ, điều sau là hợp lệ:

char arr[10];
char *p1, *p2;
p1 = arr + 10;
p2 = arr + 5;
int diff = p1 - p2;
printf("diff=%d\n", diff);   // prints 5

Một con trỏ không được trỏ trước phần tử đầu tiên.

Điều này được nêu trong phần 6.5.6p8 của tiêu chuẩn C :

Khi một biểu thức có kiểu số nguyên được thêm vào hoặc trừ khỏi một con trỏ, kết quả có kiểu của toán hạng con trỏ. Nếu toán hạng con trỏ trỏ đến một phần tử của một đối tượng mảng và mảng đủ lớn, thì kết quả trỏ đến một phần tử khác với phần tử ban đầu sao cho hiệu số của các chỉ số con của phần tử mảng kết quả và ban đầu bằng biểu thức số nguyên. Nói cách khác, nếu biểu thức Ptrỏ đến phần tử thứ i của một đối tượng mảng, thì các biểu thức (P)+N(tương đương, N+(P)) và (P)-N(trong đó Ncó giá trị n ) lần lượt trỏ đến i + n -th và i-n -th các phần tử của đối tượng mảng, miễn là chúng tồn tại. Hơn nữa, nếu biểu thức Ptrỏ đến phần tử cuối cùng của đối tượng mảng, thì biểu thức (P)+1trỏ đến phần tử cuối cùng của đối tượng mảng và nếu biểu thức Qtrỏ một phần qua phần tử cuối cùng của đối tượng mảng, thì biểu thức (Q)-1trỏ đến phần tử cuối cùng của đối tượng mảng. Nếu cả toán hạng con trỏ và kết quả đều trỏ đến các phần tử của cùng một đối tượng mảng hoặc một phần tử vượt qua phần tử cuối cùng của đối tượng mảng, thì phép đánh giá sẽ không tạo ra lỗi tràn; nếu không, hành vi là không xác định. Nếu kết quả trỏ qua phần tử cuối cùng của đối tượng mảng, nó sẽ không được sử dụng làm toán hạng của toán tử một ngôi *được đánh giá.

Lưu ý rằng phần được in đậm nói rằng một con trỏ có thể được tạo để trỏ đến một phần tử ở cuối mảng và không có gì cho phép trỏ đến bất kỳ điểm nào trước khi bắt đầu mảng.

klutt Jan 10 2021 at 08:19

Như những người khác đã chỉ ra, bạn được phép chỉ ra một quá khứ . Nhưng hãy nhớ rằng KHÔNG được phép trỏ một phần tử trước phần tử đầu tiên. Vì vậy, bạn có thể muốn cẩn thận nếu bạn viết các thuật toán đi qua các mảng ngược lại. Vì đoạn mã này không hợp lệ:

void foo(int *arr, int *end) {
    while(end-- != arr) { // Ouch, bad idea...
        // Code
    }
    // Here, end has the value arr[-1]
}

Đó là bởi vì, khi endtrỏ đến cùng một phần tử với arr, điều kiện sẽ là false, nhưng sau đó, điều kiện sẽ bị endgiảm một lần nữa và sẽ trỏ đến một phần tử trước mảng, do đó gọi ra hành vi không xác định.

Lưu ý rằng ngoài điều đó, mã hoạt động tốt. Để sửa lỗi, bạn có thể thực hiện việc này thay thế:

void foo(int *arr, int *end) {
    while(end != arr) { 
        end--; // Move end-- to inside the loop, in the very beginning

        // Code
    }

    // And here, end is equal to arr, which is perfectly fine
}

Mã trong vòng lặp sẽ hoạt động chính xác như trước. Điểm khác biệt duy nhất là lần trước endsẽ không giảm.