2009-11-30 35 views
11

Sự khác nhau giữa việc sử dụng đơn giản là void * như trái ngược với một công đoàn là gì? Ví dụ:số liên kết so với con trỏ rỗng

struct my_struct { 
    short datatype; 
    void *data; 
} 

struct my_struct { 
    short datatype; 
    union { 
     char* c; 
     int* i; 
     long* l; 
    }; 
}; 

Cả hai có thể được sử dụng để thực hiện cùng một điều, tốt hơn là nên sử dụng liên minh hoặc dấu *?

+5

tha thứ cho các cliche, nhưng kích thước quan trọng :) –

+2

@tinkertim và tỷ lệ cược là chúng sẽ có cùng kích thước. Trên một hệ thống intel 32 bit, cả hai sẽ mất 4 byte. về mặt kỹ thuật, void * có thể lớn hơn, nhưng nó hầu như không bao giờ (trừ khi double * lớn hơn ...) – Mikeage

+0

Không phải tất cả các con trỏ đều có cùng kích thước bất kể loại dữ liệu mà chúng trỏ tới không? –

Trả lời

16

Tôi đã có chính xác trường hợp trong thư viện của chúng tôi. Chúng tôi đã có một mô-đun ánh xạ chuỗi chung có thể sử dụng các kích thước khác nhau cho chỉ mục, 8, 16 hoặc 32 bit (vì lý do lịch sử). Vì vậy, mã có đầy đủ mã như sau:

if(map->idxSiz == 1) 
    return ((BYTE *)map->idx)[Pos] = ...whatever 
else 
    if(map->idxSiz == 2) 
    return ((WORD *)map->idx)[Pos] = ...whatever 
    else 
    return ((LONG *)map->idx)[Pos] = ...whatever 

Có 100 dòng giống như vậy. Trong bước đầu tiên tôi đã thay đổi nó thành một công đoàn và tôi thấy nó dễ đọc hơn.

switch(map->idxSiz) { 
    case 1: return map->idx.u8[Pos] = ...whatever 
    case 2: return map->idx.u16[Pos] = ...whatever 
    case 3: return map->idx.u32[Pos] = ...whatever 
} 

này alowed tôi để xem rõ hơn những gì đang xảy ra và sau đó tôi có thể quyết định để loại bỏ hoàn toàn các idxSiz biến thể chỉ sử dụng 32 chỉ số bit. Nhưng điều này chỉ có thể một khi mã có thể đọc được nhiều hơn. PS: Đó chỉ là một phần nhỏ trong dự án của chúng tôi, khoảng 100 nghìn dòng mã được viết bởi những người không còn tồn tại nữa. Vì vậy, những thay đổi trong mã được dần dần để không phá vỡ các ứng dụng.

Kết luận: Ngay cả khi mọi người ít được sử dụng hơn với biến thể công đoàn, tôi thích nó hơn vì nó có thể làm cho mã nhẹ hơn để đọc. Trên các dự án lớn, điều cực kỳ quan trọng là làm cho mã dễ đọc hơn, ngay cả khi chính bạn sẽ đọc nó sau này.

Sửa: Thêm bình luận, như ý kiến ​​không định dạng mã:

Sự thay đổi để chuyển đổi đến trước (điều này bây giờ là mã thực như nó đã được)

switch(this->IdxSiz) { 
    case 2: ((uint16_t*)this->iSort)[Pos-1] = (uint16_t)this->header.nUz; break; 
    case 4: ((uint32_t*)this->iSort)[Pos-1] = this->header.nUz; break; 
} 

đã được đổi thành

switch(this->IdxSiz) { 
    case 2: this->iSort.u16[Pos-1] = this->header.nUz; break; 
    case 4: this->iSort.u32[Pos-1] = this->header.nUz; break; 
} 

Tôi không nên kết hợp tất cả vẻ đẹp tôi đã làm trong mã và chỉ hiển thị bước đó.Nhưng tôi đã đăng câu trả lời của mình ở nhà mà tôi không có quyền truy cập vào mã số

+5

Tôi nghĩ rằng khả năng đọc đến từ việc sử dụng công tắc thay vì lồng nhau ifs. – swegi

+0

Thay đổi để chuyển đổi đến trước (đây là mã thực tế) chuyển (this-> IdxSiz) { trường hợp 2: ((uint16_t *) this-> iSort) [Pos-1] = (uint16_t) this-> header.nUz; phá vỡ; trường hợp 4: ((uint32_t *) this-> iSort) [Pos-1] = this-> header.nUz; phá vỡ; } đã được đổi thành chuyển đổi (this-> IdxSiz) { trường hợp 2: this-> iSort.u16 [Pos-1] = this-> header.nUz; phá vỡ; trường hợp 4: this-> iSort.u32 [Pos-1] = this-> header.nUz; phá vỡ; } Tôi không nên kết hợp tất cả vẻ đẹp tôi đã làm trong mã và chỉ hiển thị bước đó. Nhưng tôi đã đăng câu trả lời của mình ở nhà, nơi tôi không có quyền truy cập vào mã. –

11

Theo ý kiến ​​của tôi, con trỏ void và đúc rõ ràng là cách tốt hơn, bởi vì nó là rõ ràng cho mỗi lập trình viên dày dạn mục đích là gì.

Chỉnh sửa để làm rõ: Nếu tôi thấy liên minh được nói trong chương trình, tôi sẽ tự hỏi liệu tác giả có muốn hạn chế các loại dữ liệu được lưu trữ hay không. Có lẽ một số kiểm tra sanity được thực hiện có ý nghĩa chỉ trên các loại số nguyên. Nhưng nếu tôi thấy con trỏ trống, tôi trực tiếp biết rằng tác giả đã thiết kế cấu trúc dữ liệu để chứa dữ liệu tùy ý. Vì vậy, tôi có thể sử dụng nó cho các loại cấu trúc mới được giới thiệu, quá. Lưu ý rằng có thể tôi không thể thay đổi mã gốc, ví dụ: nếu nó là một phần của thư viện của bên thứ ba.

+0

mục đích và sử dụng là hai điều khác nhau khi nói đến mức tiêu thụ bộ nhớ. Một công đoàn lớn như thành viên lớn nhất của nó. –

+0

nhưng trong casee này .. tất cả các thành viên trong công đoàn là con trỏ .. Vì vậy, nó phải có cùng kích thước như một void * phải không? – FatDaemon

+1

không có gì trong spec yêu cầu con trỏ có cùng kích thước, nhưng void * phải đủ lớn để xử lý bất kỳ con trỏ nào khác. Điều đó nói rằng, trong thực tế, con trỏ nói chung là cùng kích thước – Mikeage

2

Mặc dù sử dụng công đoàn không phổ biến hiện nay, vì công đoàn chắc chắn hơn cho kịch bản sử dụng của bạn, phù hợp với tốt. Trong mẫu mã đầu tiên, nó không hiểu nội dung của dữ liệu.

5

Cách tiếp cận union yêu cầu bạn biết một ưu tiên tất cả các loại có thể được sử dụng. Cách tiếp cận void * cho phép lưu trữ kiểu dữ liệu có thể không tồn tại khi mã được đề cập được viết (mặc dù thực hiện nhiều với loại dữ liệu không xác định này có thể phức tạp, chẳng hạn như yêu cầu chuyển con trỏ đến hàm được gọi trên dữ liệu đó thay vì có thể xử lý trực tiếp).

Chỉnh sửa: Vì dường như có một số hiểu lầm về cách sử dụng loại dữ liệu không xác định: trong hầu hết các trường hợp, bạn cung cấp một số loại chức năng "đăng ký". Trong một trường hợp điển hình, bạn chuyển các con trỏ tới các hàm có thể thực hiện tất cả các thao tác bạn cần trên một mục đang được lưu trữ. Nó tạo và trả về một chỉ mục mới được sử dụng cho giá trị xác định loại. Sau đó, khi bạn muốn lưu trữ một đối tượng thuộc loại đó, bạn đặt số nhận dạng của nó thành giá trị bạn đã nhận lại từ đăng ký và khi mã hoạt động với các đối tượng cần phải làm điều gì đó với đối tượng đó, nó sẽ gọi hàm thích hợp thông qua con trỏ bạn truyền vào. Trong một trường hợp điển hình, các con trỏ tới các hàm này sẽ nằm trong một struct và nó sẽ chỉ lưu trữ (các con trỏ trỏ tới) các cấu trúc đó trong một mảng. Giá trị nhận dạng mà nó trả về từ đăng ký chỉ là chỉ mục vào mảng của các cấu trúc đó, nơi nó đã lưu trữ giá trị cụ thể này.

+0

Có, nhưng nếu yo không biết loại dữ liệu bạn không có lựa chọn, vì vậy "Cái nào?" câu hỏi sẽ là vô nghĩa. –

2

Tùy chọn của tôi sẽ là đi tuyến đường công đoàn. Các diễn viên từ void * là một công cụ cùn và truy cập vào datum thông qua một con trỏ đúng cách cho một chút an toàn hơn.

+0

nó không bảo vệ; nếu bạn lưu trữ một char * và truy cập nó như int *, trình biên dịch sẽ hoàn toàn hài lòng, mặc dù nếu nó không liên kết, bạn sẽ gặp sự cố ... – Mikeage

+0

Lưu ý rằng tôi đã nói một chút "an toàn". Sự thật là C sẽ cho phép bạn thực hiện bất kỳ sai lầm nào bạn muốn. –

+0

bit nào? buộc bạn chỉ truyền int * sang char * hoặc ngược lại? – Mikeage

2

Toss xu. Liên minh thường được sử dụng với các loại không phải con trỏ, do đó, nó trông hơi lạ ở đây. Tuy nhiên, đặc tả loại rõ ràng nó cung cấp là tài liệu ngầm phong nha. void * sẽ ổn thôi miễn là bạn luôn biết rằng bạn sẽ chỉ truy cập con trỏ. Đừng bắt đầu đặt các số nguyên trong đó và dựa vào sizeof (void *) == sizeof (int).

Tôi không cảm thấy như một trong hai cách có bất kỳ lợi thế nào so với người khác cuối cùng.

2

Đó là một chút che khuất trong ví dụ của bạn, bởi vì bạn đang sử dụng con trỏ và do đó vô hướng. Nhưng union chắc chắn không có lợi thế của nó.

Hãy tưởng tượng:

struct my_struct { 
    short datatype; 
    union { 
     char c; 
     int i; 
     long l; 
    }; 
}; 

Bây giờ bạn không cần phải lo lắng về nơi phân bổ cho phần giá trị đến từ đâu. Không có riêng biệt malloc() hoặc bất kỳ thứ gì tương tự. Và bạn có thể thấy rằng quyền truy cập vào ->c, ->i->l nhanh hơn một chút. (Mặc dù điều này chỉ có thể tạo sự khác biệt nếu có nhiều quyền truy cập này.)

+2

có lẽ những con trỏ trỏ đến một mảng ...? – Mikeage

+0

@Mikeage - Sau đó, nếu mảng có kích thước tối đa cố định và kích thước đó có thể chấp nhận được để đưa vào cấu trúc của bạn, tôi vẫn muốn sử dụng một liên kết, không chứa con trỏ nhưng chứa mảng. Nếu không tôi sẽ sử dụng con trỏ void. Vì ví dụ này không bao gồm một thành viên kích thước, tôi cho rằng đó không phải là những gì anh ta nghĩ. – asveikau

6

Thông thường, sử dụng liên kết để giữ các đối tượng thực tế hơn là con trỏ.

Tôi nghĩ rằng hầu hết các nhà phát triển C mà tôi tôn trọng sẽ không bận tâm để liên kết các con trỏ khác nhau với nhau; nếu một con trỏ có mục đích chung là cần thiết, chỉ cần sử dụng void * chắc chắn là "cách C". Ngôn ngữ hy sinh rất nhiều sự an toàn để cho phép bạn cố tình làm nổi bật các loại sự việc; xem xét những gì chúng tôi đã trả tiền cho tính năng này, chúng tôi cũng có thể sử dụng nó khi nó đơn giản hóa mã. Đó là lý do tại sao việc thoát khỏi việc đánh máy nghiêm ngặt luôn ở đó.

1

Nó thực sự phụ thuộc vào vấn đề bạn đang cố giải quyết. Nếu không có bối cảnh đó, thật sự không thể đánh giá cái nào sẽ tốt hơn. Ví dụ, nếu bạn đang cố gắng để xây dựng một container chung như một danh sách hoặc một hàng đợi có thể xử lý các loại dữ liệu tùy ý, thì cách tiếp cận con trỏ void là thích hợp hơn. OTOH, nếu bạn đang giới hạn bản thân với một tập hợp nhỏ các kiểu dữ liệu nguyên thủy, thì cách tiếp cận công đoàn có thể giúp bạn tiết kiệm thời gian và công sức.

2

Nếu bạn xây dựng mã của mình với bí danh (gcc) hoặc các tùy chọn tương tự trên trình biên dịch khác, thì bạn phải rất cẩn thận với cách bạn làm quá trình truyền của bạn. Bạn có thể đúc một con trỏ nhiều như bạn muốn, nhưng khi bạn dereference nó, loại con trỏ mà bạn sử dụng cho dereference phải phù hợp với loại ban đầu (với một số trường hợp ngoại lệ). Bạn có thể không ví dụ làm một cái gì đó như:

 
void foo(void * p) 
{ 
    short * pSubSetOfInt = (short *)p ; 
    *pSubSetOfInt = 0xFFFF ; 
} 

void goo() 
{ 
    int intValue = 0 ; 

    foo(&intValue) ; 

    printf("0x%X\n", intValue) ; 
} 

Đừng ngạc nhiên nếu điều này in 0 (nói) thay vì 0xFFFF hoặc 0xFFFF0000 như bạn có thể mong đợi khi xây dựng với tối ưu hóa. Một cách để làm cho mã này hoạt động là làm điều tương tự bằng cách sử dụng một liên minh, và mã có lẽ sẽ dễ hiểu hơn.

1

Công đoàn đặt đủ không gian cho thành viên lớn nhất, chúng không nhất thiết phải giống nhau, vì void * có kích thước cố định, trong khi đó công đoàn có thể được sử dụng cho kích thước tùy ý.

#include <stdio.h> 
#include <stdlib.h> 

struct m1 { 
    union { 
    char c[100]; 
    }; 
}; 

struct m2 { 
    void * c; 
}; 


int 
main() 
{ 
printf("sizeof m1 is %d ",sizeof(struct m1)); 
printf("sizeof m2 is %d",sizeof(struct m2)); 
exit(EXIT_SUCCESS); 
} 

Output: sizeof m1 là 100 sizeof m2 là 4

EDIT: giả sử bạn chỉ sử dụng con trỏ có cùng kích thước như void *, tôi nghĩ rằng sự kết hợp là tốt hơn, vì bạn sẽ đạt được một chút phát hiện lỗi khi cố gắng thiết lập .c với một con trỏ nguyên, v.v. void *, trừ khi bạn đang tạo phân bổ của riêng bạn, chắc chắn là nhanh chóng và dơ bẩn, tốt hơn hoặc xấu hơn.

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