Tại sao con trỏ hàm và con trỏ dữ liệu không tương thích trong C / C ++?


130

Tôi đã đọc rằng chuyển đổi một con trỏ hàm thành một con trỏ dữ liệu và ngược lại hoạt động trên hầu hết các nền tảng nhưng không được đảm bảo để hoạt động. Tại sao điều này là trường hợp? Cả hai không nên đơn giản là địa chỉ vào bộ nhớ chính và do đó tương thích?


16
Không xác định trong tiêu chuẩn C, được xác định trong POSIX. Tâm sự khác biệt.
ephemient

Tôi có một chút mới mẻ về điều này, nhưng bạn không nên thực hiện vai diễn ở phía bên phải của "="? Có vẻ như vấn đề là bạn đang gán cho một con trỏ trống. Nhưng tôi thấy rằng trang người đàn ông làm điều này, vì vậy hy vọng ai đó có thể giáo dục tôi. Tôi thấy các ví dụ trên 'mạng người đúc giá trị trả về từ dlsym, ví dụ ở đây: daniweb.com/forums/thread62561.html
JasonWoust

9
Lưu ý những gì POSIX nói trong phần về Kiểu dữ liệu : §2.12.3 Loại con trỏ. Tất cả các loại con trỏ hàm sẽ có cùng biểu diễn với con trỏ kiểu tới void. Chuyển đổi một con trỏ hàm thành void *sẽ không làm thay đổi biểu diễn. Một void *giá trị kết quả từ một chuyển đổi như vậy có thể được chuyển đổi trở lại loại con trỏ hàm ban đầu, sử dụng một biểu thức rõ ràng, mà không mất thông tin. Lưu ý : Tiêu chuẩn ISO C không yêu cầu điều này, nhưng nó được yêu cầu cho sự phù hợp POSIX.
Jonathan Leffler

2
đây là câu hỏi trong phần GIỚI THIỆU của trang web này .. :) :) Hẹn gặp bạn câu hỏi tại đây
ZooZ

1
@KeithThndry: thế giới thay đổi - và POSIX cũng vậy. Những gì tôi đã viết vào năm 2012 không còn áp dụng vào năm 2018. Tiêu chuẩn POSIX đã thay đổi verbiage. Bây giờ nó được liên kết với dlsym()- lưu ý phần cuối của phần 'Sử dụng ứng dụng' có ghi: Lưu ý rằng việc chuyển đổi từ một void *con trỏ sang một con trỏ hàm như trong: fptr = (int (*)(int))dlsym(handle, "my_function"); không được xác định bởi tiêu chuẩn ISO C. Tiêu chuẩn này yêu cầu chuyển đổi này để hoạt động chính xác trong việc thực hiện tuân thủ.
Jonathan Leffler

Câu trả lời:


171

Một kiến ​​trúc không phải lưu trữ mã và dữ liệu trong cùng một bộ nhớ. Với kiến ​​trúc Harvard, mã và dữ liệu được lưu trữ trong bộ nhớ hoàn toàn khác nhau. Hầu hết các kiến ​​trúc là các kiến ​​trúc Von Neumann với mã và dữ liệu trong cùng một bộ nhớ nhưng C không giới hạn bản thân chỉ một số loại kiến ​​trúc nhất định nếu có thể.


15
Ngoài ra, ngay cả khi mã và dữ liệu được lưu trữ ở cùng một vị trí trong phần cứng vật lý, phần mềm và truy cập bộ nhớ thường ngăn không cho chạy dữ liệu dưới dạng mã mà không có sự "phê duyệt" của hệ điều hành. DEP và tương tự.
Michael Grachot

15
Ít nhất cũng quan trọng như việc có các không gian địa chỉ khác nhau (có thể quan trọng hơn) là các con trỏ hàm có thể có một biểu diễn khác với các con trỏ dữ liệu.
Michael Burr

14
Bạn thậm chí không cần phải có kiến ​​trúc Harvard để có con trỏ mã và dữ liệu bằng cách sử dụng các không gian địa chỉ khác nhau - mô hình bộ nhớ "Nhỏ" của DOS đã làm điều này (gần con trỏ với CS != DS).
phê

1
ngay cả các bộ xử lý hiện đại cũng phải vật lộn với hỗn hợp như bộ đệm hướng dẫn và dữ liệu thường được xử lý riêng, ngay cả khi hệ điều hành cho phép bạn viết mã ở đâu đó.
PypeBros

3
@EricJ. Cho đến khi bạn gọi VirtualProtect, cho phép bạn đánh dấu các vùng dữ liệu là có thể thực thi được.
Dietrich Epp

37

Một số máy tính có (có) không gian địa chỉ riêng cho mã và dữ liệu. Trên phần cứng như vậy, nó không hoạt động.

Ngôn ngữ được thiết kế không chỉ cho các ứng dụng máy tính để bàn hiện tại mà còn cho phép nó được thực hiện trên một bộ phần cứng lớn.


Có vẻ như ủy ban ngôn ngữ C không bao giờ có ý định void*trở thành một con trỏ hoạt động, họ chỉ muốn một con trỏ chung cho các đối tượng.

Cơ sở lý luận C99 nói:

6.3.2.3 Con trỏ
C hiện đã được triển khai trên một loạt các kiến ​​trúc. Trong khi một số kiến ​​trúc này có các con trỏ thống nhất có kích thước của một số loại số nguyên, mã di động tối đa không thể có bất kỳ sự tương ứng cần thiết nào giữa các loại con trỏ khác nhau và các loại số nguyên. Trên một số triển khai, con trỏ thậm chí có thể rộng hơn bất kỳ loại số nguyên nào.

Việc sử dụng void*(con trỏ con trỏ đến tên lửa void) làm kiểu con trỏ đối tượng chung là một phát minh của Ủy ban C89. Việc chấp nhận loại này được kích thích bởi mong muốn chỉ định các đối số nguyên mẫu hàm có thể chuyển đổi lặng lẽ các con trỏ tùy ý (như trong fread) hoặc khiếu nại nếu loại đối số không khớp chính xác (như trong strcmp). Không có gì được nói về con trỏ tới các hàm, có thể không tương thích với các con trỏ đối tượng và / hoặc số nguyên.

Lưu ý Không có gì được nói về con trỏ đến các chức năng trong đoạn cuối. Chúng có thể khác với các con trỏ khác, và ủy ban nhận thức được điều đó.


Tiêu chuẩn có thể làm cho chúng tương thích mà không gây rối với điều này bằng cách đơn giản làm cho các kiểu dữ liệu có cùng kích thước và đảm bảo rằng việc gán cho một rồi quay lại sẽ dẫn đến cùng một giá trị. Họ làm điều này với void *, đây là loại con trỏ duy nhất tương thích với mọi thứ.
Edward Strange

15
@CrazyEddie Bạn không thể gán con trỏ hàm cho a void *.
ouah

4
Tôi có thể sai trên void * chấp nhận con trỏ hàm, nhưng điểm vẫn còn. Bit là bit. Tiêu chuẩn có thể yêu cầu kích thước của các loại khác nhau có thể chứa dữ liệu từ nhau và việc gán sẽ được đảm bảo để hoạt động ngay cả khi chúng được sử dụng trong các phân đoạn bộ nhớ khác nhau. Lý do sự không tương thích này tồn tại là điều này KHÔNG được đảm bảo bởi tiêu chuẩn và do đó dữ liệu có thể bị mất trong bài tập.
Edward Strange

5
Nhưng yêu cầu sizeof(void*) == sizeof( void(*)() )sẽ lãng phí không gian trong trường hợp con trỏ hàm và con trỏ dữ liệu có kích thước khác nhau. Đây là trường hợp phổ biến trong thập niên 80, khi tiêu chuẩn C đầu tiên được viết.
Rob

8
@RichardChambers: Các không gian địa chỉ khác nhau cũng có thể có độ rộng địa chỉ khác nhau , chẳng hạn như Atmel AVR sử dụng 16 bit cho hướng dẫn và 8 bit cho dữ liệu; trong trường hợp đó, sẽ khó chuyển đổi từ con trỏ dữ liệu (8 bit) sang chức năng (16 bit) và quay lại. C được cho là dễ thực hiện; một phần của sự dễ dàng đó đến từ việc để dữ liệu và các con trỏ chỉ dẫn không tương thích với nhau.
John Bode

30

Đối với những người nhớ MS-DOS, Windows 3.1 trở lên, câu trả lời khá dễ dàng. Tất cả những thứ này được sử dụng để hỗ trợ một số mô hình bộ nhớ khác nhau, với sự kết hợp các đặc điểm khác nhau cho mã và dữ liệu con trỏ.

Vì vậy, ví dụ cho mô hình Compact (mã nhỏ, dữ liệu lớn):

sizeof(void *) > sizeof(void(*)())

và ngược lại trong mô hình Trung bình (mã lớn, dữ liệu nhỏ):

sizeof(void *) < sizeof(void(*)())

Trong trường hợp này, bạn không có bộ lưu trữ riêng cho mã và ngày nhưng vẫn không thể chuyển đổi giữa hai con trỏ (không sử dụng công cụ sửa đổi __near và __far không chuẩn).

Ngoài ra, không có gì đảm bảo rằng ngay cả khi các con trỏ có cùng kích thước, chúng chỉ đến cùng một thứ - trong mô hình bộ nhớ nhỏ của DOS, cả mã và dữ liệu được sử dụng gần các con trỏ, nhưng chúng chỉ vào các phân đoạn khác nhau. Vì vậy, chuyển đổi một con trỏ hàm thành một con trỏ dữ liệu sẽ không cung cấp cho bạn một con trỏ có bất kỳ mối quan hệ nào với hàm đó và do đó không có cách sử dụng cho chuyển đổi như vậy.


Re: "chuyển đổi một con trỏ hàm thành một con trỏ dữ liệu sẽ không cung cấp cho bạn một con trỏ có bất kỳ mối quan hệ nào với hàm đó và do đó không thể sử dụng cho một chuyển đổi như vậy": Điều này hoàn toàn không tuân theo. Chuyển đổi int*một void*cung cấp cho bạn một con trỏ mà bạn thực sự không thể làm gì được, nhưng vẫn hữu ích để có thể thực hiện chuyển đổi. (Điều này là do void*có thể lưu trữ bất kỳ con trỏ đối tượng nào , do đó có thể được sử dụng cho các thuật toán chung không cần biết chúng giữ loại nào. Điều tương tự cũng có thể hữu ích cho các con trỏ hàm, nếu nó được cho phép.)
ruakh

4
@ruakh: Trong trường hợp chuyển đổi int *thành void *, void *ít nhất được đảm bảo trỏ đến cùng một đối tượng như ban đầu int *- vì vậy điều này rất hữu ích cho các thuật toán chung truy cập vào đối tượng trỏ, như int n; memcpy(&n, src, sizeof n);. Trong trường hợp chuyển đổi một con trỏ hàm thành một con trỏ void *không trỏ đến hàm, nó không hữu ích cho các thuật toán như vậy - điều duy nhất bạn có thể làm là chuyển đổi void *trở lại thành một con trỏ hàm, vì vậy bạn có thể cũng chỉ cần sử dụng một con trỏ unionchứa a void *và chức năng.
phê

@caf: Đủ công bằng. Cảm ơn đã chỉ ra rằng. Và đối với vấn đề đó, ngay cả khi void* đã chỉ ra chức năng, tôi cho rằng nó sẽ là một ý tưởng tồi cho mọi người để truyền nó cho memcpy. :-P
ruakh

Sao chép từ trên: Lưu ý POSIX nói gì trong các loại dữ liệu : §2.12.3 Các loại con trỏ. Tất cả các loại con trỏ hàm sẽ có cùng biểu diễn với con trỏ kiểu tới void. Chuyển đổi một con trỏ hàm thành void *sẽ không làm thay đổi biểu diễn. Một void *giá trị kết quả từ một chuyển đổi như vậy có thể được chuyển đổi trở lại loại con trỏ hàm ban đầu, sử dụng một biểu thức rõ ràng, mà không mất thông tin. Lưu ý : Tiêu chuẩn ISO C không yêu cầu điều này, nhưng nó được yêu cầu cho sự phù hợp POSIX.
Jonathan Leffler

@caf Nếu chỉ cần chuyển qua một số cuộc gọi lại biết loại phù hợp, tôi chỉ quan tâm đến an toàn khứ hồi, không phải bất kỳ mối quan hệ nào khác mà các giá trị được chuyển đổi có thể có.
Ded repeatator

23

Các con trỏ tới void được cho là có thể chứa một con trỏ tới bất kỳ loại dữ liệu nào - nhưng không nhất thiết phải là một con trỏ tới một hàm. Một số hệ thống có các yêu cầu khác nhau về con trỏ đến chức năng so với con trỏ tới dữ liệu (ví dụ: có DSP với địa chỉ khác nhau cho dữ liệu so với mã, mô hình trung bình trên MS-DOS sử dụng con trỏ 32 bit cho mã nhưng chỉ con trỏ 16 bit cho dữ liệu) .


1
nhưng sau đó, hàm dlsym () sẽ trả về một cái gì đó không phải là void *. Ý tôi là, nếu khoảng trống * không đủ lớn cho con trỏ hàm, thì chúng ta đã không tìm hiểu chưa?
Manav

1
@Knickerkicker: Vâng, có lẽ. Nếu bộ nhớ phục vụ, loại trả về từ dlsym đã được thảo luận ở độ dài, có thể là 9 hoặc 10 năm trước, trong danh sách email của Opengroup. Mặc dù vậy, tôi không nhớ những gì (nếu có gì) đến từ nó.
Jerry Coffin

1
bạn đúng. Đây có vẻ là một bản tóm tắt khá hay (mặc dù đã lỗi thời) về quan điểm của bạn.
Manav


2
@LegoStormtroopr: Thật thú vị khi 21 người đồng ý với ý tưởng bỏ phiếu, nhưng chỉ có khoảng 3 người thực sự làm như vậy. :-)
Jerry Coffin

13

Ngoài những gì đã được nói ở đây, thật thú vị khi xem POSIX dlsym():

Tiêu chuẩn ISO C không yêu cầu các con trỏ tới các chức năng có thể được truyền qua lại cho các con trỏ tới dữ liệu. Thật vậy, tiêu chuẩn ISO C không yêu cầu một đối tượng có kiểu void * có thể giữ một con trỏ tới một hàm. Tuy nhiên, các triển khai hỗ trợ phần mở rộng XSI yêu cầu một đối tượng có kiểu void * có thể giữ một con trỏ tới một hàm. Tuy nhiên, kết quả của việc chuyển đổi một con trỏ thành một hàm thành một con trỏ sang một kiểu dữ liệu khác (trừ void *) vẫn chưa được xác định. Lưu ý rằng các trình biên dịch tuân theo tiêu chuẩn ISO C được yêu cầu để tạo cảnh báo nếu việc chuyển đổi từ con trỏ void * sang con trỏ hàm được thử như trong:

 fptr = (int (*)(int))dlsym(handle, "my_function");

Do vấn đề được lưu ý ở đây, một phiên bản trong tương lai có thể thêm một hàm mới để trả về các con trỏ hàm hoặc giao diện hiện tại có thể không được dùng để sử dụng hai hàm mới: một hàm trả về các con trỏ dữ liệu và một hàm khác trả về các con trỏ hàm.


điều đó có nghĩa là việc sử dụng dlsym để lấy địa chỉ của hàm hiện không an toàn? Hiện tại có một cách an toàn để làm điều đó?
gex sát thương

4
Điều đó có nghĩa là hiện tại POSIX yêu cầu từ ABI nền tảng rằng cả hai chức năng và con trỏ dữ liệu có thể được truyền tới void*và quay lại một cách an toàn .
Maxim Egorushkin

@gexinf Điều đó có nghĩa là các triển khai tuân thủ POSIX đã tạo ra một phần mở rộng cho ngôn ngữ, mang lại ý nghĩa xác định thực hiện cho hành vi không xác định theo bản thân tiêu chuẩn. Nó thậm chí còn được liệt kê là một trong những phần mở rộng phổ biến của tiêu chuẩn C99, phần J.5.7.
David Hammen

1
@DavidHammen Nó không phải là một phần mở rộng cho ngôn ngữ, mà là một yêu cầu bổ sung mới. C không yêu cầu void*phải tương thích với một con trỏ hàm, trong khi POSIX thì không.
Maxim Egorushkin

9

C ++ 11 có một giải pháp cho sự không phù hợp lâu dài giữa C / C ++ và POSIX liên quan đến dlsym(). Người ta có thể sử dụng reinterpret_castđể chuyển đổi một con trỏ hàm thành / từ một con trỏ dữ liệu miễn là việc triển khai hỗ trợ tính năng này.

Từ tiêu chuẩn, 5.2.10 para. 8, "chuyển đổi một con trỏ hàm thành một loại con trỏ đối tượng hoặc ngược lại được hỗ trợ có điều kiện." 1.3.5 định nghĩa "được hỗ trợ có điều kiện" là "chương trình xây dựng mà việc triển khai không bắt buộc phải hỗ trợ".


Người ta có thể, nhưng người ta không nên. Một trình biên dịch tuân thủ phải tạo ra một cảnh báo cho điều đó (do đó sẽ gây ra lỗi, xem -Werror). Một giải pháp tốt hơn (và không phải UB) là truy xuất một con trỏ đến đối tượng được trả về bởi dlsym(tức là void**) và chuyển đổi nó thành một con trỏ thành con trỏ hàm . Vẫn được xác định thực hiện nhưng không còn gây ra cảnh báo / lỗi .
Konrad Rudolph

3
@KonradRudolph: Không đồng ý. Từ ngữ "được hỗ trợ có điều kiện" được viết riêng để cho phép dlsymGetProcAddressbiên dịch mà không cần cảnh báo.
MSalters

@MSalters Ý bạn là gì, không đồng ý với nhau? Hoặc tôi đúng hoặc sai. Các tài liệu dlsym dứt khoát nói rằng “trình biên dịch phù hợp với tiêu chuẩn ISO C được yêu cầu để tạo ra một cảnh báo nếu một sự chuyển đổi từ một con trỏ * khoảng trống để một con trỏ hàm được cố gắng”. Điều này không để lại nhiều chỗ cho đầu cơ. Và GCC (với -pedantic) không cảnh báo. Một lần nữa, không có suy đoán có thể.
Konrad Rudolph

1
Theo dõi: Tôi nghĩ bây giờ tôi hiểu. Đó không phải là UB. Đó là định nghĩa thực hiện. Tôi vẫn không chắc chắn liệu cảnh báo có phải được tạo hay không - có thể là không. Ồ tốt
Konrad Rudolph

2
@KonradRudolph: Tôi không đồng ý với "không nên" của bạn, đó là một ý kiến. Câu trả lời đã đề cập cụ thể đến C ++ 11 và tôi là thành viên của C ++ CWG tại thời điểm vấn đề được giải quyết. C99 thực sự có từ ngữ khác nhau, được hỗ trợ có điều kiện là một phát minh C ++.
MSalters

7

Tùy thuộc vào kiến ​​trúc đích, mã và dữ liệu có thể được lưu trữ trong các vùng bộ nhớ riêng biệt không tương thích về mặt vật lý.


"Khác biệt về thể chất" Tôi hiểu, nhưng bạn có thể giải thích rõ hơn về sự khác biệt "không tương thích về cơ bản". Như tôi đã nói trong câu hỏi, không phải là một con trỏ trống được cho là lớn như bất kỳ loại con trỏ nào - hoặc đó là một giả định sai về phía tôi.
Manav

@KnickerKicker: void *đủ lớn để chứa bất kỳ con trỏ dữ liệu nào, nhưng không nhất thiết là bất kỳ con trỏ hàm nào.
ephemient

1
trở lại tương lai: P
SSpoke

5

không xác định không nhất thiết có nghĩa là không được phép, điều đó có thể có nghĩa là người triển khai trình biên dịch có nhiều tự do hơn để làm điều đó theo cách họ muốn.

Chẳng hạn, một số kiến ​​trúc có thể không khả thi - không xác định cho phép họ vẫn có thư viện 'C' phù hợp ngay cả khi bạn không thể làm điều này.


5

Giải pháp khác:

Giả sử POSIX đảm bảo chức năng và con trỏ dữ liệu có cùng kích thước và biểu diễn (tôi không thể tìm thấy văn bản cho điều này, nhưng ví dụ OP trích dẫn cho thấy ít nhất họ dự định thực hiện yêu cầu này), nên làm như sau:

double (*cosine)(double);
void *tmp;
handle = dlopen("libm.so", RTLD_LAZY);
tmp = dlsym(handle, "cos");
memcpy(&cosine, &tmp, sizeof cosine);

Điều này tránh vi phạm các quy tắc răng cưa bằng cách đi qua char []biểu diễn, được phép đặt bí danh tất cả các loại.

Một cách tiếp cận khác:

union {
    double (*fptr)(double);
    void *dptr;
} u;
u.dptr = dlsym(handle, "cos");
cosine = u.fptr;

Nhưng tôi muốn giới thiệu memcpycách tiếp cận nếu bạn muốn hoàn toàn chính xác 100% C.


5

Chúng có thể là các loại khác nhau với các yêu cầu không gian khác nhau. Việc gán cho người ta có thể cắt ngang giá trị của con trỏ để gán lại kết quả trong một cái gì đó khác nhau.

Tôi tin rằng chúng có thể là các loại khác nhau vì tiêu chuẩn không muốn giới hạn các triển khai có thể giúp tiết kiệm không gian khi không cần thiết hoặc khi kích thước có thể khiến CPU phải sử dụng thêm crap để sử dụng nó, v.v ...


3

Giải pháp thực sự di động duy nhất là không sử dụng dlsymcho các chức năng, và thay vào đó sử dụng dlsymđể có được một con trỏ tới dữ liệu có chứa các con trỏ hàm. Ví dụ: trong thư viện của bạn:

struct module foo_module = {
    .create = create_func,
    .destroy = destroy_func,
    .write = write_func,
    /* ... */
};

và sau đó trong ứng dụng của bạn:

struct module *foo = dlsym(handle, "foo_module");
foo->create(/*...*/);
/* ... */

Ngẫu nhiên, dù sao đây cũng là một thực hành thiết kế tốt và giúp dễ dàng hỗ trợ cả tải động qua dlopenvà liên kết tĩnh tất cả các mô-đun trên các hệ thống không hỗ trợ liên kết động hoặc nơi tích hợp người dùng / hệ thống không muốn sử dụng liên kết động.


2
Đẹp! Mặc dù tôi đồng ý rằng điều này có vẻ dễ bảo trì hơn, nhưng đối với tôi, vẫn chưa rõ ràng về cách tôi liên kết tĩnh trên đầu trang này. Bạn có thể xây dựng?
Manav

2
Nếu mỗi mô-đun có foo_modulecấu trúc riêng (có tên duy nhất), bạn chỉ cần tạo một tệp bổ sung với một mảng struct { const char *module_name; const struct module *module_funcs; }và một hàm đơn giản để tìm kiếm bảng này cho mô-đun bạn muốn "tải" và trả về con trỏ bên phải, sau đó sử dụng ở vị trí của dlopendlsym.
R .. GitHub DỪNG GIÚP ICE

@R .. Đúng, nhưng nó thêm chi phí bảo trì bằng cách phải duy trì cấu trúc mô-đun.
dùng877329

3

Một ví dụ hiện đại về nơi con trỏ hàm có thể khác nhau về kích thước so với con trỏ dữ liệu: con trỏ hàm thành viên lớp C ++

Trích dẫn trực tiếp từ https://bloss.msdn.microsoft.com/oldnewthing/20040209-00/?p=40713/

class Base1 { int b1; void Base1Method(); };
class Base2 { int b2; void Base2Method(); };
class Derived : public Base1, Base2 { int d; void DerivedMethod(); };

Bây giờ có hai thiscon trỏ có thể .

Một con trỏ tới hàm thành viên Base1có thể được sử dụng làm con trỏ tới hàm thành viên Derived, vì cả hai đều sử dụng cùng một this con trỏ. Nhưng một con trỏ tới hàm thành viên Base2không thể được sử dụng như là một con trỏ tới hàm thành viên của Derived, vì this con trỏ cần phải được điều chỉnh.

Có nhiều cách để giải quyết điều này. Đây là cách trình biên dịch Visual Studio quyết định xử lý nó:

Một con trỏ tới một hàm thành viên của một lớp được thừa kế thực sự là một cấu trúc.

[Address of function]
[Adjustor]

Kích thước của hàm con trỏ đến thành viên của một lớp sử dụng nhiều kế thừa là kích thước của một con trỏ cộng với kích thước của a size_t.

tl; dr: Khi sử dụng nhiều kế thừa, một con trỏ tới hàm thành viên có thể (tùy thuộc vào trình biên dịch, phiên bản, kiến ​​trúc, v.v.) thực sự được lưu trữ dưới dạng

struct { 
    void * func;
    size_t offset;
}

mà rõ ràng là lớn hơn a void *.


2

Trên hầu hết các kiến ​​trúc, con trỏ tới tất cả các loại dữ liệu thông thường có cùng một biểu diễn, do đó, việc truyền giữa các loại con trỏ dữ liệu là không có.

Tuy nhiên, có thể hình dung rằng các con trỏ hàm có thể yêu cầu một đại diện khác, có lẽ chúng lớn hơn các con trỏ khác. Nếu void * có thể giữ các con trỏ hàm, điều này có nghĩa là đại diện của void * sẽ phải có kích thước lớn hơn. Và tất cả các phôi con trỏ dữ liệu đến / từ void * sẽ phải thực hiện bản sao bổ sung này.

Như ai đó đã đề cập, nếu bạn cần điều này, bạn có thể đạt được nó bằng cách sử dụng một liên minh. Nhưng hầu hết việc sử dụng void * chỉ dành cho dữ liệu, do đó, sẽ rất khó để tăng tất cả việc sử dụng bộ nhớ của họ chỉ trong trường hợp con trỏ hàm cần được lưu trữ.


-1

Tôi biết rằng điều này đã không được nhận xét trên kể từ năm 2012, nhưng tôi nghĩ rằng nó sẽ hữu ích để thêm rằng tôi làm quen một kiến trúc có rất gợi ý không phù hợp cho dữ liệu và các chức năng kể từ khi một cuộc gọi trên rằng séc kiến trúc đặc ân và mang thông tin thêm. Không có số lượng đúc sẽ giúp. Đó là Mill .


Câu trả lời này là sai. Ví dụ, bạn có thể chuyển đổi một con trỏ hàm thành một con trỏ dữ liệu và đọc từ nó (nếu bạn có quyền đọc từ địa chỉ đó, như bình thường). Kết quả có ý nghĩa nhiều như nó, ví dụ như trên x86.
Manuel Jacob
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.