2009-03-19 37 views
11

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]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ại pointer 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?

Trả lời

10

Mảng là một loại bộ nhớ. Cú pháp, nó được sử dụng như một con trỏ, nhưng về thể chất, không có biến "con trỏ" trong cấu trúc đó - chỉ là ba int.Mặt khác, con trỏ int là một kiểu dữ liệu thực được lưu trữ trong cấu trúc. Do đó, khi bạn thực hiện dàn diễn viên, bạn có thể * làm cho ptr đảm nhận giá trị của phần tử đầu tiên trong mảng, cụ thể là 1.

* Tôi không chắc chắn đây là hành vi được xác định thực sự. làm việc trên hầu hết các hệ thống phổ biến ít nhất.

+0

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

+2

"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. –

3

Lý do sai của tôi ở đâu/tôi không hiểu gì?

type_1::array (không đúng cú pháp C) không phải là int *; nó là int [3].

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?

typedef struct 
{  
    int ptr[]; 
} type2_t; 

Điều đó tuyên bố thành viên mảng linh hoạt. Từ tiêu chuẩn C (6.7.2.1 đoạn 16):

Tuy nhiên, khi a. Toán tử (hoặc ->) có toán hạng bên trái (là con trỏ đến) một cấu trúc với thành viên mảng linh hoạt và tên toán hạng đúng là thành viên, nó hoạt động như thể thành viên đó được thay thế bằng mảng dài nhất (với cùng loại phần tử) sẽ không làm cho cấu trúc lớn hơn đối tượng được truy cập; độ lệch của mảng phải giữ nguyên giá trị của thành viên mảng linh hoạt, ngay cả khi giá trị này khác với giá trị của mảng thay thế.

I.e, nó có thể bí danh type1_t::array đúng cách.

11

Hãy tha thứ cho tôi nếu tôi bỏ qua bất kỳ điều gì trong phân tích của bạn. Nhưng tôi nghĩ rằng lỗi cơ bản trong tất cả đó là giả định sai lầm

type2_p-> ptr có loại "con trỏ tới int" và giá trị là địa chỉ bắt đầu của my_test.

Không có gì làm cho nó có giá trị đó. Thay vào đó, rất có thể nó trỏ đến một nơi nào đó để

0x00000001 

Vì những gì bạn làm là giải thích các byte tạo nên dãy số nguyên đó làm con trỏ. Sau đó, bạn thêm một cái gì đó vào nó và subscript.

Ngoài ra, tôi rất nghi ngờ việc truyền của bạn tới cấu trúc khác thực sự hợp lệ (như trong, được đảm bảo để hoạt động). Bạn có thể cast và sau đó đọc một chuỗi ban đầu chung của một trong hai struct nếu cả hai đều là thành viên của một union. Nhưng chúng không có trong ví dụ của bạn. Bạn cũng có thể truyền tới một con trỏ tới thành viên đầu tiên. Ví dụ:

typedef struct { 
    int array[3]; 
} type1_t; 

type1_t f = { { 1, 2, 3 } }; 

int main(void) { 
    int (*arrayp)[3] = (int(*)[3])&f; 
    (*arrayp)[0] = 3; 
    assert(f.array[0] == 3); 
    return 0; 
} 
+0

Cảm ơn bạn đã chỉ ra chính xác giả định không chính xác của tôi (kiểu 'type2_t * type2_p = (type2_t *) & my_test;' type cast). Xin lỗi vì không chấp nhận câu trả lời của bạn, nhưng tôi sẽ chọn câu trả lời của Chuck mà tôi tìm thấy một chút chính xác. – hlovdal

0

Nó phải được xác định hành vi. Hãy suy nghĩ về nó trong bộ nhớ.

Để đơn giản, giả sử my_test có địa chỉ 0x80000000.

type1_p == 0x80000000 
&type1_p->my_array[0] == 0x80000000 // my_array[0] == 1 
&type1_p->my_array[1] == 0x80000004 // my_array[1] == 2 
&type1_p->my_array[2] == 0x80000008 // my_array[2] == 3 

Khi bạn cast nó để type2_t,

type2_p == 0x80000000 
&type2_p->ptr == 0x8000000 // type2_p->ptr == 1 
type2_p->ptr[0] == *(type2_p->ptr) == *1 

Để làm những gì bạn muốn, bạn sẽ phải hoặc là tạo ra một cấu trúc thứ cấp & gán địa chỉ của mảng để ptr (ví dụ type2_p-> ptr = type1_p-> my_array) hoặc khai báo ptr như một mảng (hoặc một mảng độ dài biến đổi, ví dụ int ptr []).

Ngoài ra, bạn có thể truy cập vào các yếu tố một cách xấu xí: (& type2_p-> ptr) [0], (& type2_p-> ptr) [1]. Tuy nhiên, hãy cẩn thận tại đây kể từ (& type2_p-> ptr) [0] thực sự sẽ là int *, không phải là int. Trên nền tảng 64 bit, ví dụ: (& type2_p-> ptr) [0] thực sự sẽ là 0x100000002 (4294967298).

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