2009-05-27 38 views
52

Chuẩn C cho phép các con trỏ đến các loại khác nhau có kích thước khác nhau, ví dụ: sizeof(char*) != sizeof(int*) được cho phép. Tuy nhiên, nó yêu cầu rằng nếu một con trỏ được chuyển đổi thành void* và sau đó được chuyển đổi về kiểu gốc của nó, nó phải so sánh với giá trị ban đầu của nó. Do đó, nó theo logic một cách hợp lý rằng sizeof(void*) >= sizeof(T*) cho tất cả các loại T, đúng không? Trên các nền tảng phổ biến nhất được sử dụng ngày nay (x86, PPC, ARM và 64-bit, vv), kích thước của tất cả các con trỏ bằng kích thước đăng ký gốc (4 hoặc 8 byte), bất kể đánh máy. Có bất kỳ nền tảng bí truyền hoặc nhúng nào trong đó các con trỏ đến các loại khác nhau có thể có kích thước khác nhau không? Tôi đặc biệt hỏi về dữ liệu con trỏ, mặc dù tôi cũng muốn biết liệu có các nền tảng có chức năng hàm có kích thước không bình thường hay không.Có nền tảng nào mà con trỏ đến các loại khác nhau có kích thước khác nhau không?

Tôi chắc chắn là không phải hỏi về các hàm trỏ-thành viên của C++ và các hàm con trỏ tới thành viên. Những người có kích thước bất thường trên nền tảng phổ biến và thậm chí có thể thay đổi trong một nền tảng, tùy thuộc vào thuộc tính của lớp con trỏ (không đa hình, thừa kế đơn, đa thừa kế, thừa kế ảo hoặc loại không đầy đủ).

+0

Tò mò, phần nào của tiêu chuẩn cho phép các kích thước con trỏ khác nhau? Bạn có nhớ gửi phần đó – JaredPar

+1

Nit-pick: "kiểu gốc nguyên gốc" trong C phải là int, hiếm khi 64 bit ngay cả trên nền tảng 64 bit, AFAIK. Nói cách khác, LP64 phổ biến hơn ILP64. – unwind

+0

@JaredPar: Tôi không chắc chắn chính xác vị trí của nó trong tiêu chuẩn, nhưng trang này http://www.lysator.liu.se/c/rat/d9.html#4-9-6-1 đề cập đến của nó, liên quan đến định dạng% p fprintf specifier. @unwind: s/nguyên số nguyên/kích thước đăng ký bản địa/ –

Trả lời

44

Answer from the C FAQ:

Thủ tướng Chính 50 loạt sử dụng phân khúc 07.777, offset 0 cho con trỏ null, ít nhất là cho PL/I. Các mô hình sau sử dụng phân khúc 0, bù 0 cho các con trỏ null trong C, đòi hỏi các chỉ lệnh mới như TCNP (Test C Null Pointer), hiển nhiên như là một đỉnh cho tất cả các mã C đã viết kém chưa thực hiện mà đưa ra các giả định không chính xác. Các máy Prime cũ hơn, được giải quyết bằng văn bản cũng nổi tiếng vì yêu cầu các con trỏ byte lớn hơn (char * 's) hơn con trỏ từ (int *' s). Dòng sản phẩm MV Eclipse từ Data General có ba định dạng con trỏ được hỗ trợ kiến ​​trúc (từ, byte và bit con trỏ), hai trong số đó được sử dụng bởi trình biên dịch C: con trỏ byte cho char * và void *, và con trỏ từ cho mọi thứ khác. Vì lý do lịch sử trong quá trình tiến hóa của dòng 32-bit MV từ đường Nova 16 bit, các con trỏ từ và con trỏ byte có các bit bảo vệ bù đắp, vô hướng và vòng ở các vị trí khác nhau trong từ. Chuyển một định dạng con trỏ không khớp đến một hàm dẫn đến lỗi bảo vệ. Cuối cùng, trình biên dịch MV C đã thêm nhiều tùy chọn tương thích để cố gắng xử lý mã có lỗi không khớp kiểu con trỏ.

Một số khung chính của Honeywell-Bull sử dụng mẫu bit 06000 cho các con trỏ null (nội bộ).

CDC Cyber ​​180 Series có các con trỏ 48 bit bao gồm vòng, đoạn và bù. Hầu hết người dùng (trong vòng 11) đều có con trỏ null là 0xB00000000000. Nó phổ biến trên các máy bổ sung CDC cũ để sử dụng một từ một bit như một lá cờ đặc biệt cho tất cả các loại dữ liệu, kể cả các địa chỉ không hợp lệ.

Dòng HP 3000 cũ sử dụng lược đồ địa chỉ khác cho địa chỉ byte thay vì địa chỉ từ; giống như một số máy trên nó do đó sử dụng các biểu diễn khác nhau cho các con trỏ char * và void * so với các con trỏ khác.

Máy tượng trưng Lisp, một kiến ​​trúc được gắn thẻ, thậm chí không có con trỏ số thông thường; nó sử dụng các cặp (về cơ bản là một xử lý không tồn tại) như là một con trỏ null C.

Tùy thuộc vào kiểu bộ nhớ `'đang sử dụng, bộ xử lý 8086-gia đình (PC tương thích) có thể sử dụng con trỏ dữ liệu 16 bit và chức năng 32 bit con trỏ hoặc ngược lại.

Một số máy in màu 64 bit đại diện cho int * trong 48 bit thấp hơn của từ ; char * cũng sử dụng một số trong số 16 bit trên để chỉ ra địa chỉ byte trong một từ.

Liên kết bổ sung: A message from Chris Torek với các chi tiết khác về một số máy này.

+2

+1 Tại sao đây không phải là câu trả lời được chấp nhận? .. – dcow

+0

@David Cowden Nghi ngờ câu trả lời này được viết 4 tháng sau khi được chấp nhận. Vì OP đã yêu cầu _an_ và nhận nó - câu trả lời đó đã được chấp nhận. Bài đăng này, chắc chắn hoàn chỉnh hơn, xứng đáng với tỷ lệ bỏ phiếu cao. Có thể các câu trả lời được đánh giá cao không được chấp nhận xứng đáng một cộng đồng "chấp nhận ghi đè"? Điều đó nghe như một câu hỏi "meta". – chux

+0

@chux có lẽ tốt hơn để chỉ thông báo cho người hỏi rằng một câu trả lời cho câu hỏi của họ đã vượt qua rất nhiều câu trả lời được chấp nhận và để họ xem xét lại. Cuối cùng, tùy thuộc vào người hỏi để xác định câu trả lời nào trả lời tốt nhất câu hỏi của họ. Tôi chỉ đơn giản nhận xét để nhấn mạnh một thực tế rằng tôi * rất * rất thích câu trả lời này (= – dcow

11

Quay lại những năm vàng của DOS, 8088 và bộ nhớ phân đoạn, thông thường phải chỉ định "mô hình bộ nhớ", ví dụ: tất cả các mã sẽ phù hợp với 64k (một phân đoạn) nhưng dữ liệu có thể kéo dài nhiều phân đoạn; điều này có nghĩa là một con trỏ hàm sẽ là 2 byte, một con trỏ dữ liệu, 4 ​​byte. Không chắc liệu có ai vẫn đang lập trình cho các loại máy đó không, có thể một số vẫn tồn tại trong các ứng dụng nhúng.

+0

Chúng không phải là không phổ biến trong thế giới nhúng. DOS vẫn được sử dụng rất nhiều. –

+1

@Nils, Gần đây, tôi đã phỏng vấn một phiên bản mới (EE) và kinh nghiệm lắp ráp chính của anh ấy (từ việc sử dụng nhúng), hóa ra là với Intel 8051 và Freescale 6811 - 8-bit con cháu của các CPU mà tôi học ở trường đại học vào những năm 70 (!), và thậm chí sau đó chúng tôi đã khao khát những người mạnh hơn như Zilog Z80. Vì vậy, 8088 và DOS sẽ là một bước tiến lớn ở đó ...! –

28

Không hoàn toàn những gì bạn đang yêu cầu, nhưng trở lại trong 16-bit DOS/Windows ngày, bạn đã có sự khác biệt giữa một con trỏ và một con trỏ xa, sau này là 32-bit.

tôi có thể có cú pháp sai ...

int *pInt = malloc(sizeof(int)); 
int far *fpInt = _fmalloc(sizeof(int)); 

printf("pInt: %d, fpInt: %d\n", sizeof(pInt), sizeof(fpInt)); 

Output:

pint: 2, 4 fpInt

+4

Bah, tôi hoàn toàn quên đi những con trỏ gần và xa. Tôi đã nhận thức được sự tồn tại của họ, nhưng khi tôi đang viết câu hỏi này, họ hoàn toàn trượt tâm trí của tôi. –

+4

16bit DOS có phải là một ví dụ với trình biên dịch phù hợp tiêu chuẩn không? – sellibitze

+0

@sellibitze: Bạn nói đúng - thuộc tính 'far' không nằm trong bất kỳ chuẩn C nào, vì vậy đoạn trích trong câu trả lời không phải là tiêu chuẩn hợp lệ C. Vì vậy, điều này được cho là không hoàn toàn trả lời câu hỏi (dường như hỏi về chuẩn C) - nhưng nó vẫn là một câu trả lời có giá trị IMHO. – sleske

7

Người ta có thể dễ dàng tưởng tượng một máy kiến ​​trúc Harvard có kích cỡ khác nhau cho con trỏ hàm và tất cả các con trỏ khác. Không biết ví dụ ...

+0

Kiến trúc Harvard thường được sử dụng trong các bộ vi xử lý nhúng (PIC) vào năm 2013. – chux

+0

Có, các kiến ​​trúc Harvard xuất hiện trong rất nhiều chip nhúng. Nhưng bạn có biết một trong đó thực hiện các con trỏ hàm có kích thước khác với các con trỏ khác trên cùng một nền tảng không? – dmckee

+2

PIC16 (trình biên dịch CCS) đã sử dụng bộ nhớ RAM 9-10 bitof (bitmap trang 1-2 bit và bù đắp 8 bit.) Cồng kềnh để thậm chí 'memcpy()'. Dữ liệu không bay hơi bị mắc kẹt trong ROM (tôi nghĩ rằng 14-16 bit ngay cả địa chỉ trỏ tới 1 byte) và sử dụng một 'memcpy()/strcpy()' đặc biệt. Các hàm rất khó để có được/sử dụng thông qua một con trỏ hàm, cũng có địa chỉ bit 14-16 vào một từ chỉ dẫn một nửa 14 bit, vì vậy địa chỉ phải là ngay cả. Chắc chắn tôi đã nhận được câu chuyện này messed lên như tôi sử dụng một PIC24 nhiều hơn nữa thường xuyên và để cho các trình biên dịch đối phó với các địa chỉ biết, tôi, coder, không phải wily-nilly trộn các loại con trỏ. – chux

13

Do đó, nó theo logic một cách hợp lý rằng sizeof(void*) >= sizeof(T*) cho tất cả các loại T, đúng không?

Điều đó không nhất thiết phải tuân theo, vì sizeof là về đại diện bộ nhớ và không phải tất cả các mẫu bit đều phải là giá trị hợp lệ. Tôi nghĩ rằng bạn có thể viết triển khai tuân thủ ở đó sizeof(int*) == 8, sizeof(void*) == 4, nhưng không có nhiều hơn 2^32 giá trị có thể cho một int *. Không chắc chắn tại sao bạn muốn.

+7

Nó phụ thuộc vào định nghĩa của bạn về "hợp lý"; v) – Potatoswatter

+0

Làm thế nào 'malloc()' có thể được sử dụng để phân bổ, và sau đó gán, một 'int *'? Ngoài ra, bạn có nghĩa là "Không có nhiều hơn 2^32 giá trị có thể cho một _int * _" hoặc "cho một int"? – smci

+0

@smci: Tôi có nghĩa là cho một 'int *'.Điểm được cung cấp không có nhiều hơn '2^32' giá trị pháp lý cho' int * 'thì không quan trọng kích thước của' int * 'là gì, hoặc giá trị nào là giá trị pháp lý, việc triển khai có thể vẫn thực hiện chuyển đổi từ 'int *' thành 4 byte 'void *' và ngược lại. Bạn có thể phân bổ và gán một 'int *' bằng cách sử dụng 'malloc' như sau:' int ** ppint = malloc (sizeof (* ppint)); * ppint = 0; '. Nhưng tôi không thấy điều đó liên quan đến câu hỏi hoặc câu trả lời của tôi. –

7

Con trỏ cận và vẫn được sử dụng trên một số bộ vi điều khiển nhúng với flash hoặc RAM, để cho phép bạn trỏ đến dữ liệu trong cùng một trang (gần con trỏ) hoặc một trang khác (con trỏ xa, lớn hơn vì nó bao gồm thông tin trang).

Ví dụ, bộ vi điều khiển HCS12 của Freescale sử dụng kiến ​​trúc Von Neumann 16 bit, có nghĩa là không có địa chỉ nào có thể lớn hơn 16 bit. Bởi vì giới hạn này sẽ đặt vào số lượng không gian mã có sẵn, có một trang đăng ký 8-bit.

Vì vậy, để trỏ đến dữ liệu trong cùng một trang mã, bạn chỉ cần chỉ định địa chỉ 16 bit; đây là một con trỏ gần.

Để trỏ đến dữ liệu trong trang mã khác, bạn phải bao gồm cả số trang 8 bit và địa chỉ 16 bit trong trang đó, dẫn đến con trỏ xa 24 bit.

6

Có thể kích thước của con trỏ đến dữ liệu khác với con trỏ đến hàm chẳng hạn. Nó là phổ biến cho điều này xảy ra trong bộ vi xử lý cho hệ thống nhúng. Các máy kiến ​​trúc Harvard như dmckee đã đề cập khiến việc này dễ xảy ra.

Nó chỉ ra rằng nó làm cho phụ trợ gcc một nỗi đau để phát triển!:)

Chỉnh sửa: Tôi không thể đi vào chi tiết của máy cụ thể mà tôi đang nói đến nhưng hãy để tôi thêm lý do tại sao máy Harvard làm cho điều này dễ xảy ra. Kiến trúc Harvard có lưu trữ và đường dẫn khác nhau để hướng dẫn và dữ liệu, do đó nếu bus cho lệnh là 'lớn hơn' cho dữ liệu, bạn sẽ có một con trỏ hàm có kích thước lớn hơn con trỏ tới dữ liệu!

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