2012-07-09 25 views
13

Tôi muốn khai báo một mảng liên tục có thể được truy cập từ nhiều tệp C và nội dung của trình biên dịch có thể được biên dịch, mà không nhân đôi bộ nhớ trong nhiều đơn vị biên dịch. Hiệu suất là rất quan trọng trong ứng dụng của tôi.Mảng C cố định không thể in không có sự sao chép bộ nhớ

Phụ lục 1:

header.h: 
static const int arr[2] = { 1, 2 }; 

file1.c: 
#include "header.h" 
void file1() { printf("%d\n", arr[0]); } 

file2.c: 
#include "header.h" 
int file2() { for (int i = 0; i < 2; i++) printf("%d\n", arr[i]); } 

Trong trường hợp đó, trình biên dịch có thể thay thế arr[0] bởi 1 trong file1. Tuy nhiên, vì arr được khai báo là static const, bộ nhớ của nó được sao chép trong cả hai tệp C. AFAIK tiêu chuẩn C yêu cầu các địa chỉ mảng phải khác nhau trong cả hai tệp. Tôi đã xác minh điều này dưới Linux bằng cách in ra các địa chỉ. Không có sự hợp nhất liên kết xảy ra ngay cả với -fmerge-all-constants trong gcc.

Phụ lục 2:

header.h: 
extern const int arr[2]; 

file1.c: 
#include "header.h" 
void file1() { printf("%d\n", arr[0]); } 

file2.c: 
#include "header.h" 
const int arr[2] = { 1, 2 }; 
int file2() { for (int i = 0; i < 2; i++) printf("%d\n", arr[i]); } 

Trong trường hợp đó, không có sự trùng lặp bộ nhớ xảy ra nhưng arr[0] không inlined.

Tôi xem phạm vi khả năng hiển thị được xác định theo tiêu chuẩn C là thiếu sót. Như vậy, một giải pháp làm việc dưới Linux/gcc vi phạm tiêu chuẩn C là chấp nhận được với tôi.

+0

Trên thực tế, 'file2' cũng cho phép nội tuyến mảng. 'gcc -O9' bỏ vòng lặp và trực tiếp đẩy hai giá trị. – aschepler

+0

@aschepler: Có. Trên hệ thống của tôi, với -O2, trình biên dịch dừng nội tuyến khi tôi đặt ba hoặc nhiều phần tử trong mảng. –

Trả lời

3

Một điều bạn có thể thử:

const int arr[2] __attribute__((weak)) = { 1, 2 }; 

Bây giờ mảng vẫn còn tồn tại trong mỗi * đối tượng .o, nhưng khi những đối tượng được liên kết với nhau trong một chương trình, GNU ld sẽ giảm họ chỉ là một đoạn thông thường Dữ liệu.

Nếu bạn chưa có một điều như vậy, bạn có thể muốn trong một số tập tin tiêu đề phổ biến:

#ifndef __GNUC__ 
#define __attribute__(x) 
#endif 
+0

Đã xác nhận !! Công việc nội tuyến và địa chỉ mảng giống nhau trong mỗi tệp . Cảm ơn rất nhiều! –

4

Không có cách nào tiêu chuẩn để đạt được điều đó trong "cổ điển" C (đề cập đến C89/90), thật không may. Trong C89/90 bạn bị giới hạn ở hai phương pháp bạn mô tả, với ưu và khuyết điểm tương ứng của chúng, miễn là bạn nhấn mạnh vào việc sử dụng một mảng.

Trong C99, mọi thứ tốt hơn. Trong C99 bạn có thể sử dụng cái gọi là hợp chất literals, nghĩa là chỉ xác định arr như một macro trong file header

#define arr ((const int []) { 1, 2 }) 

và sau đó hy vọng rằng trình biên dịch sẽ "inline" mảng. Các ký tự hợp chất của các loại const được xử lý giống như chuỗi ký tự: các lần xuất hiện khác nhau của chữ giống nhau trong chương trình có thể được trình biên dịch hợp nhất thành một thể hiện của đối tượng thực (nếu trình biên dịch không trực tiếp).

AFAIK, trình biên dịch GCC hỗ trợ các ký tự hợp chất dưới dạng tiện ích mở rộng ngay cả ở các chế độ không phải C99.

+0

Tôi không thể làm cho phương pháp của bạn hoạt động. Tôi nhận được các giá trị khác nhau (và không hợp lệ) nếu tôi in trực tiếp "địa chỉ" của mảng. Nếu tôi khai báo một hàm trong mỗi tệp nhận một con trỏ làm đối số và chuyển mảng vào từng hàm, tôi cũng nhận được các giá trị con trỏ khác nhau nhưng hợp lệ. Nội dung mảng là chính xác khi tôi in nó ra trong mỗi chức năng. –

+0

Tôi cũng đã thử in ra các giá trị mảng trực tiếp (không qua địa chỉ con trỏ). Về tháo gỡ, tôi thấy trình biên dịch thực sự lưu trữ tất cả các phần tử mảng trên ngăn xếp (mov 1, mov 2, vv) trước khi in ra các giá trị. Cảm ơn sự giúp đỡ mặc dù, tôi không biết về C99 mảng hợp chất literals. –

+0

@Laurent Birtz: Các ký tự hợp chất được cho là tạo các đối tượng * cục bộ * khi được sử dụng trong phạm vi chức năng và các đối tượng * tĩnh * khi được sử dụng trong phạm vi tệp. Điều này ngụ ý rằng các giá trị thực sự sẽ được lưu trữ "trên ngăn xếp" từ quan điểm trừu tượng. Tuy nhiên, tôi mong đợi trình biên dịch có thể tối ưu hóa điều này, tức là "nội tuyến" các giá trị mảng và/hoặc "hợp nhất" các phiên bản khác nhau thành một. – AnT

1

Sử dụng selectanyvariable attribute và cung cấp cho mảng của bạn liên kết bên ngoài (tức là không khai báo static) . Điều này sẽ giữ giá trị mảng trong tiêu đề sao cho nó có thể được inlined đúng và thuộc tính selectany sẽ yêu cầu trình liên kết tự ý chọn một trong các định nghĩa là định nghĩa thực và loại bỏ các định nghĩa khác (vì chúng đều giống nhau , nó sẽ không thành vấn đề).

Ví dụ:

const int arr[] __attribute__((selectany)) = {1, 2}; 

EDIT: Đây dường như chỉ hoạt động trên các mục tiêu Windows; thuộc tính weak không hoạt động thay vì thử nghiệm nhanh mà tôi đã thực hiện với GCC của Cygwin, ở chỗ nó tạo ra nhiều bản sao của mảng trong phân đoạn dữ liệu kết quả.

+0

tài liệu gcc nói: Thuộc tính 'selectany' chỉ khả dụng trên các mục tiêu Microsoft Windows. – aschepler

+0

D'oh, bạn nói đúng. Dường như thuộc tính 'weak' không hoạt động trong một thử nghiệm nhanh mà tôi đã làm - nó tạo ra nhiều bản sao của mảng trong phân đoạn dữ liệu. –

+0

Tốt để biết nó có thể làm điều đó trên Windows quá. Cảm ơn. –

4

Tôi nghĩ rằng phân tích của bạn có phần sai. Khi bạn in địa chỉ của arr, bạn buộc trình biên dịch giữ hai bản sao. GCC sẽ loại bỏ cả hai bản sao nếu bạn không làm điều này.

Cách tốt hơn để xác định những gì mà trình liên kết có và không loại bỏ là xem các đối tượng thực trong tệp đầu ra. Trong Linux, chương trình nm sẽ cho bạn biết điều này.

Nếu tôi biên dịch mã của bạn (phần trình bày 1) với 'gcc (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1':

gcc -std=c99 -g3 -O6 -fmerge-all-constants file1.c file2.c main.c 

sau đó tôi sử dụng nm -a a.out | grep '\<arr\>' để tìm kiếm nó trong bảng biểu tượng :

$ nm -a a.out|grep '\<arr\>'|wc -l 
0 

trong thực tế, nếu bạn cố gắng để tìm thấy nó trong gdb, bạn tìm thấy gì:

(gdb) b file1 
Breakpoint 1 at 0x400540: file /usr/include/x86_64-linux-gnu/bits/stdio2.h, line 105. 
(gdb) r 
Starting program: a.out 
Breakpoint 1, file1() at file1.c:5 
5 void file1() { printf("%d\n", arr[0]); } 
(gdb) print arr 
$1 = <optimized out> 

Trình biên dịch đã hoàn toàn tối ưu hóa nó.

Nếu tôi thêm printf("%p\n",arr); đến đầu file1()file2() và biên dịch nó theo cùng một cách, sau đó nm -a a.out|grep '\<arr\>' lợi nhuận hai tài liệu tham khảo để arr:

$ nm -a a.out|grep '\<arr\>'|wc -l 
2 
$ nm -a a.out|grep '\<arr\>' 
00000000004006c8 r arr 
00000000004006d0 r arr 
+0

Bạn nói đúng, gcc xử lý trường hợp đặc biệt này một cách khéo léo, ngày nay. Nhưng bạn không đề xuất một giải pháp thực sự cho vấn đề. Các trình biên dịch đầu tiên khác có thể đối xử hoàn toàn khác. Sau đó, hằng số mà bạn không được phép in được sử dụng khá hạn chế. –

+1

@ JensGustedt: OP muốn có giải pháp làm việc cho GCC và Linux. Trong thực tế, bất kỳ loại nội tuyến nào cũng phụ thuộc vào trình biên dịch. Thậm chí việc khai báo một hàm 'inline' chỉ là một gợi ý cho trình biên dịch; tiêu chuẩn C99 không yêu cầu chức năng thực sự được gạch chân. Giải pháp này là tổng quát hơn bạn cho nó tín dụng. Đề cập đến các giá trị ** của mảng **, bao gồm cả việc in các giá trị này, vẫn sẽ tối ưu hóa mảng đó. Tuy nhiên, khi bạn chuyển một con trỏ tới một phần tử, thì mảng đó phải thực sự tồn tại và trình biên dịch bị ràng buộc bởi các quy tắc 'static' của C99. – sfstewman

+0

Không có ý định phạm tội. Nó chỉ là một giải pháp cho vấn đề tồn tại. Câu trả lời của AndreyT cho một câu trả lời hoàn toàn phù hợp với gcc, nhưng cũng làm việc với các trình biên dịch phù hợp khác. Sau đó, bạn bị nhầm lẫn, 'inline' không phải là một gợi ý" chỉ "mà chỉ là một từ sai. Nó thay đổi thuộc tính hiển thị của hàm. https://gustedt.wordpress.com/2010/11/29/myth-and-reality-about-inline-in-c99/ –