Tôi nghĩ mình thực sự hiểu điều này, và đọc lại tiêu chuẩn (ISO 9899: 1990) chỉ xác nhận sự hiểu biết rõ ràng của tôi, vì vậy bây giờ tôi hỏi ở đây.Pointer vs array in C, khác biệt không tầm thường
Các chương trình bị treo sau:
#include <stdio.h>
#include <stddef.h>
typedef struct {
int array[3];
} type1_t;
typedef struct {
int *ptr;
} type2_t;
type1_t my_test = { {1, 2, 3} };
int main(int argc, char *argv[])
{
(void)argc;
(void)argv;
type1_t *type1_p = &my_test;
type2_t *type2_p = (type2_t *) &my_test;
printf("offsetof(type1_t, array) = %lu\n", offsetof(type1_t, array)); // 0
printf("my_test.array[0] = %d\n", my_test.array[0]);
printf("type1_p->array[0] = %d\n", type1_p->array[0]);
printf("type2_p->ptr[0] = %d\n", type2_p->ptr[0]); // this line crashes
return 0;
}
So sánh các biểu thức my_test.array[0]
và type2_p->ptr[0]
theo giải thích của tôi về tiêu chuẩn:
6.3.2.1 Mảng subscripting
"Định nghĩa của subscript operator [] là E1 [E2] là giống hệt với (* ((E1) + (E2))). "
Áp dụng điều này mang lại:
my_test.array[0]
(*((E1)+(E2)))
(*((my_test.array)+(0)))
(*(my_test.array+0))
(*(my_test.array))
(*my_test.array)
*my_test.array
type2_p->ptr[0]
*((E1)+(E2)))
(*((type2_p->ptr)+(0)))
(*(type2_p->ptr+0))
(*(type2_p->ptr))
(*type2_p->ptr)
*type2_p->ptr
type2_p->ptr
có kiểu "con trỏ đến int" và giá trị là địa chỉ bắt đầu của my_test
. Do đó, *type2_p->ptr
sẽ đánh giá thành một đối tượng số nguyên có bộ nhớ ở cùng một địa chỉ mà my_test
có.
Tiếp tục:
6.2.2.1 lvalues, mảng, và định danh chức năng
"Trừ khi nó là toán hạng của toán tử sizeof hoặc unary & điều hành, ..., an lvalue có loại
array of type
được chuyển đổi thành biểu thức có loạipointer to type
trỏ đến phần tử ban đầu của đối tượng mảng và không phải là một giá trị. "
my_test.array
có loại "mảng int" và được mô tả ở trên được chuyển thành "con trỏ thành int" với địa chỉ của phần tử đầu tiên làm giá trị. *my_test.array
do đó đánh giá một đối tượng số nguyên có lưu trữ ở cùng một địa chỉ mà phần tử đầu tiên trong mảng.
Và cuối cùng
6.5.2.1 Cấu trúc và đoàn specifiers
Một con trỏ tới một đối tượng cấu trúc, chuyển đổi phù hợp, trỏ tới thành viên ban đầu của nó ..., và ngược lại. Có thể có phần đệm chưa đặt tên trong một đối tượng cấu trúc , nhưng không phải ở số bắt đầu, khi cần thiết để đạt được sự liên kết thích hợp .
Kể từ khi thành viên đầu tiên của type1_t
là mảng, địa chỉ bắt đầu của đó và toàn bộ đối tượng type1_t
cũng giống như mô tả ở trên. Do đó, sự hiểu biết của tôi do đó *type2_p->ptr
đánh giá là một số nguyên có bộ nhớ có cùng địa chỉ với thành phần đầu tiên trong mảng và do đó giống hệt với *my_test.array
. Tuy nhiên, điều này không thể xảy ra, bởi vì chương trình bị treo thường xuyên trên solaris, cygwin và linux với các phiên bản gcc 2.95.3, 3.4.4 và 4.3.2, vì vậy bất kỳ vấn đề môi trường nào cũng hoàn toàn nằm ngoài câu hỏi.
Lý do sai của tôi ở đâu/tôi không hiểu gì? Làm cách nào để khai báo type2_t để tạo điểm ptr cho thành viên đầu tiên của mảng?
Nó chắc chắn là hành vi được xác định. Địa chỉ của ptr giống như địa chỉ của my_array. my_array thực sự là một con trỏ vào cấu trúc, trong khi ptr chỉ đơn giản là một con trỏ nguyên trong một cấu trúc. – Vitali
"hành vi được xác định" không có nghĩa là "điều gì đó xảy ra", điều đó có nghĩa là "điều gì đó xảy ra được xác định theo tiêu chuẩn". Loại punning là hành vi không xác định. Nếu bạn muốn nhìn thấy một cái gì đó đáng ngạc nhiên xảy ra khi bạn gõ pun, kick lên tối ưu hóa một notch hoặc hai trên trình biên dịch của bạn. –