Số học con trỏ cho con trỏ void trong C


176

Khi một con trỏ đến một loại đặc biệt (ví dụ int, char, float, ..) được tăng lên, giá trị của nó được tăng lên bởi kích thước của kiểu dữ liệu. Nếu một voidcon trỏ trỏ tới dữ liệu có kích thước xđược tăng lên, làm thế nào để nó tới điểm xbyte phía trước? Làm thế nào để trình biên dịch biết để thêm xvào giá trị của con trỏ?



3
Câu hỏi nghe có vẻ như giả định rằng trình biên dịch (/ run-time) biết loại đối tượng mà con trỏ được đặt và thêm kích thước của nó vào con trỏ. Đó là một quan niệm sai lầm hoàn toàn: nó chỉ biết địa chỉ.
PJTraill

2
"Nếu một voidcon trỏ trỏ tới dữ liệu có kích thước xđược tăng lên, làm thế nào để nó tới điểm xbyte phía trước?" Nó không. Tại sao những người có câu hỏi như vậy không thể kiểm tra chúng trước khi hỏi - ít nhất là ở mức tối thiểu để họ kiểm tra xem nó có thực sự biên dịch hay không, điều này không. -1, không thể tin điều này có +100 và -0.
gạch dưới

Câu trả lời:


287

Kết luận cuối cùng: số học trên a void*bất hợp pháp trong cả C và C ++.

GCC cho phép nó như một phần mở rộng, xem Số học trên void- và Con trỏ hàm (lưu ý rằng phần này là một phần của chương "Phần mở rộng C" của hướng dẫn sử dụng). Clang và ICC có thể cho phép void*số học cho các mục đích tương thích với GCC. Các trình biên dịch khác (như MSVC) không cho phép số học trên void*và GCC không cho phép nó nếu -pedantic-errorscờ được chỉ định hoặc nếu -Werror-pointer-arithcờ được chỉ định (cờ này hữu ích nếu cơ sở mã của bạn cũng phải biên dịch với MSVC).

Tiêu chuẩn C nói

Trích dẫn được lấy từ dự thảo n1256.

Mô tả của tiêu chuẩn về trạng thái hoạt động bổ sung:

6.5.6-2: Ngoài ra, cả hai toán hạng sẽ có kiểu số học hoặc một toán hạng sẽ là một con trỏ tới một loại đối tượng và một toán hạng khác sẽ có kiểu nguyên.

Vì vậy, câu hỏi ở đây là liệu void*con trỏ tới "loại đối tượng" hay tương đương, liệu đó có phải voidlà "loại đối tượng" hay không. Định nghĩa cho "loại đối tượng" là:

6.2.5.1: Các loại được phân vùng thành các loại đối tượng (loại mô tả đầy đủ các đối tượng), loại chức năng (loại mô tả chức năng) và loại không đầy đủ (loại mô tả các đối tượng nhưng thiếu thông tin cần thiết để xác định kích thước của chúng).

Và tiêu chuẩn được định nghĩa voidlà:

6.2.5-19: voidLoại bao gồm một tập hợp các giá trị trống; nó là một loại không đầy đủ không thể hoàn thành.

voidlà một loại không đầy đủ, nó không phải là một loại đối tượng. Do đó, nó không phải là một toán hạng hợp lệ cho một hoạt động bổ sung.

Do đó, bạn không thể thực hiện số học con trỏ trên một voidcon trỏ.

Ghi chú

Ban đầu, người ta cho rằng void*số học được cho phép, vì những phần này của tiêu chuẩn C:

6.2.5-27: Một con trỏ tới void sẽ có cùng các yêu cầu biểu diễn và căn chỉnh giống như một con trỏ tới một kiểu ký tự.

Tuy nhiên,

Các yêu cầu đại diện và căn chỉnh tương tự có nghĩa là ngụ ý khả năng thay thế lẫn nhau như các đối số cho các hàm, trả về các giá trị từ các hàm và các thành viên của hiệp hội.

Vì vậy, điều này có nghĩa printf("%s", x)có cùng ý nghĩa cho dù xcó loại char*hay không void*, nhưng điều đó không có nghĩa là bạn có thể thực hiện số học trên a void*.

Lưu ý của biên tập viên: Câu trả lời này đã được chỉnh sửa để phản ánh kết luận cuối cùng.


7
Từ tiêu chuẩn C99: (6.5.6.2) Ngoài ra, cả hai toán hạng sẽ có kiểu số học hoặc một toán hạng sẽ là một con trỏ tới một loại đối tượng và loại kia sẽ có kiểu nguyên. (6.2.5.19) Loại khoảng trống bao gồm một tập hợp các giá trị trống; nó là một loại không đầy đủ không thể hoàn thành. Tôi nghĩ rằng điều đó làm rõ rằng void*số học con trỏ không được phép. GCC có một phần mở rộng cho phép làm điều này.
Công việc

1
nếu bạn không còn nghĩ rằng câu trả lời của bạn hữu ích, bạn có thể xóa nó đi.
phê

1
Câu trả lời này rất hữu ích mặc dù nó đã chứng minh là sai vì nó chứa bằng chứng thuyết phục rằng con trỏ void không có nghĩa là số học.
Ben Flynn

1
Đây là một câu trả lời hay, nó có kết luận đúng và những trích dẫn cần thiết, nhưng những người đi đến câu hỏi này đã đưa ra kết luận sai vì họ không đọc đến cuối câu trả lời. Tôi đã chỉnh sửa nó để làm cho nó rõ ràng hơn.
Dietrich Epp

1
Clang và ICC không cho phép void*số học (ít nhất là theo mặc định).
Serge Podobry

61

Số học void*con trỏ không được phép trên con trỏ.


16
Số học +1 Con trỏ chỉ được xác định cho các loại đối tượng (hoàn thành) . voidlà một loại không đầy đủ mà không bao giờ có thể được hoàn thành theo định nghĩa.
schot

1
@schot: Chính xác. Hơn nữa, số học con trỏ chỉ được xác định trên một con trỏ tới một phần tử của một đối tượng mảng và chỉ khi kết quả của hoạt động sẽ là một con trỏ tới một phần tử trong cùng một mảng hoặc một phần tử qua phần tử cuối cùng của mảng đó. Nếu những điều kiện đó không được đáp ứng, đó là hành vi không xác định. (Từ tiêu chuẩn C99 6.5.6.8)
Công việc

1
Rõ ràng nó không phải như vậy với gcc 7.3.0. Trình biên dịch chấp nhận p + 1024, trong đó p là void *. Và kết quả giống như ((char *) p) + 1024
zzz777 24/03/19

@ zzz777 là tiện ích mở rộng GCC, hãy xem liên kết trong câu trả lời được bình chọn hàng đầu.
Ruslan

19

truyền nó tới một con trỏ char tăng con trỏ của bạn về phía trước x byte phía trước.


4
Quan tâm làm gì? Chỉ cần đặt nó vào đúng loại - điều cần luôn luôn được biết - và tăng lên bằng 1. Truyền tới char, tăng dần xvà sau đó diễn giải lại giá trị mới vì một số loại khác là cả hành vi vô nghĩa và không xác định.
gạch dưới

9
Nếu bạn đang viết chức năng sắp xếp của mình, theo đó man 3 qsortnên có void qsort(void *base, size_t nmemb, size_t size, [snip]), thì bạn không có cách nào để biết "đúng loại"
alisianoi

15

Các tiêu chuẩn C không cho phép khoảng trống con trỏ số học. Tuy nhiên, GNU C được phép bằng cách xem xét kích thước của void1.

Tiêu chuẩn C11 §6.2.5

Đoạn - 19

Các voidloại bao gồm một tập rỗng của các giá trị; nó là một loại đối tượng không đầy đủ không thể hoàn thành.

Chương trình sau đang hoạt động tốt trong trình biên dịch GCC.

#include<stdio.h>

int main()
{
    int arr[2] = {1, 2};
    void *ptr = &arr;
    ptr = ptr + sizeof(int);
    printf("%d\n", *(int *)ptr);
    return 0;
}

Có thể các trình biên dịch khác tạo ra một lỗi.


14

Bạn không thể thực hiện số học con trỏ trên void *các loại, vì lý do chính xác này!


8

Bạn phải chuyển nó sang một loại con trỏ khác trước khi thực hiện số học con trỏ.


7

Con trỏ trống có thể trỏ đến bất kỳ khối bộ nhớ. Do đó trình biên dịch không biết có bao nhiêu byte tăng / giảm khi chúng ta thử số học con trỏ trên một con trỏ void. Do đó, con trỏ void phải là kiểu chữ đầu tiên cho một kiểu đã biết trước khi chúng có thể tham gia vào bất kỳ số học con trỏ nào.

void *p = malloc(sizeof(char)*10);
p++; //compiler does how many where to pint the pointer after this increment operation

char * c = (char *)p;
c++;  // compiler will increment the c by 1, since size of char is 1 byte.

-1

Trình biên dịch biết theo kiểu cast. Đưa ra một void *x:

  • x+1thêm một byte vào x, con trỏ chuyển sang bytex+1
  • (int*)x+1thêm sizeof(int)byte, con trỏ đi đến bytex + sizeof(int)
  • (float*)x+1thêm sizeof(float)byte, v.v.

Mặc dù mục đầu tiên không có khả năng di động và chống lại Galateo của C / C ++, tuy nhiên nó vẫn đúng ngôn ngữ C, nghĩa là nó sẽ biên dịch thành thứ gì đó trên hầu hết các trình biên dịch có thể cần một cờ thích hợp (như -Wpulum-arith)


2
Althought the first item is not portable and is against the Galateo of C/C++Thật. it is nevertheless C-language-correctSai. Đây là nghi ngờ! Số học con trỏ trên void *là bất hợp pháp về mặt cú pháp, không nên biên dịch và tạo ra hành vi không xác định nếu có. Nếu một lập trình viên bất cẩn có thể làm cho nó biên dịch bằng cách vô hiệu hóa một số cảnh báo, đó không phải là lý do.
gạch dưới

@underscore_d: Tôi nghĩ rằng một số trình biên dịch được sử dụng để cho phép nó như một phần mở rộng, vì nó thuận tiện hơn rất nhiều so với việc phải sử dụng unsigned char*để ví dụ thêm một sizeofgiá trị vào một con trỏ.
supercat
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.