Các biến ngăn xếp có được căn chỉnh bởi GCC __attribute __ ((căn chỉnh (x))) không?


88

tôi có mã sau:

#include <stdio.h>

int
main(void)
{
        float a[4] __attribute__((aligned(0x1000))) = {1.0, 2.0, 3.0, 4.0};
        printf("%p %p %p %p\n", &a[0], &a[1], &a[2], &a[3]);
}

Và tôi có kết quả sau:

0x7fffbfcd2da0 0x7fffbfcd2da4 0x7fffbfcd2da8 0x7fffbfcd2dac

Tại sao địa chỉ của a[0]không phải là bội số 0x1000?

Chính xác là __attribute__((aligned(x)))gì? Tôi đã hiểu sai lời giải thích này ?

Tôi đang sử dụng gcc 4.1.2.

Câu trả lời:


98

Tôi tin rằng vấn đề là mảng của bạn nằm trên ngăn xếp và trình biên dịch của bạn quá cũ để hỗ trợ các biến ngăn xếp được căn chỉnh quá mức. GCC 4.6 trở lên đã sửa lỗi đó .

C11 / C ++ 11 alignas(64) float a[4];Chỉ hoạt động cho bất kỳ sức mạnh nào của 2 căn chỉnh.
GNU C __attribute__((aligned(x)))cũng vậy khi bạn đang sử dụng nó.

(Trong C11, #include <stdalign.h>cho #define alignas _Alignas: cppref ).


Nhưng trong trường hợp căn chỉnh rất lớn của bạn, đến ranh giới trang 4k, bạn có thể không muốn nó nằm trên ngăn xếp.

Bởi vì con trỏ ngăn xếp có thể là bất kỳ thứ gì khi hàm bắt đầu, không có cách nào để căn chỉnh mảng mà không phân bổ nhiều hơn bạn cần và điều chỉnh nó. (Các trình biên dịch sẽ and rsp, -4096hoặc tương đương và không sử dụng bất kỳ byte nào trong số 0 đến 4088 byte được phân bổ; phân nhánh xem không gian đó có đủ lớn hay không sẽ có thể thực hiện được nhưng không được thực hiện vì các căn chỉnh lớn hơn nhiều so với kích thước của mảng hoặc các cục bộ khác không phải là trường hợp bình thường.)

Nếu bạn di chuyển mảng ra khỏi hàm và thành một biến toàn cục, nó sẽ hoạt động. Điều khác bạn có thể làm là giữ nó như một biến cục bộ (đó là một điều rất tốt), nhưng hãy tạo ra nó static. Điều này sẽ ngăn nó được lưu trữ trên ngăn xếp. Lưu ý rằng cả hai cách này đều không an toàn theo chuỗi hoặc an toàn đệ quy, vì sẽ chỉ có một bản sao của mảng.

Với mã này:

#include <stdio.h>

float a[4] __attribute__((aligned(0x1000))) = {1.0, 2.0, 3.0, 4.0};

int
main(void)
{
        printf("%p %p %p %p\n", &a[0], &a[1], &a[2], &a[3]);
}

Tôi hiểu điều này:

0x804c000 0x804c004 0x804c008 0x804c00c

đó là những gì được mong đợi. Với mã gốc của bạn, tôi chỉ nhận được các giá trị ngẫu nhiên như bạn đã làm.


11
+1 câu trả lời đúng. Một giải pháp thay thế là làm cho mảng cục bộ tĩnh. Căn chỉnh trên ngăn xếp luôn là một vấn đề và tốt nhất bạn nên tập thói quen tránh nó.
Dan Olson

Ồ vâng, tôi không nghĩ đến việc làm cho nó tĩnh. Đó là một ý tưởng hay vì nó ngăn chặn sự va chạm tên tuổi. Tôi sẽ chỉnh sửa câu trả lời của mình.
Zifre

3
Lưu ý rằng việc đặt nó ở trạng thái tĩnh cũng làm cho nó không chạy lại và không an toàn cho chuỗi.
ArchaeaSoftware,

3
Ngoài ra gcc 4.6+ xử lý điều này một cách chính xác ngay cả trên ngăn xếp.
texthell

1
Câu trả lời này đã từng đúng, nhưng bây giờ thì không. gcc cũ như 4.6, có thể cũ hơn, biết cách căn chỉnh con trỏ ngăn xếp để triển khai chính xác C11 / C ++ 11 alignas(64)hoặc bất cứ thứ gì trên các đối tượng có lưu trữ tự động. Và tất nhiên GNU C__attribute((aligned((64)))
Peter Cordes

41

Đã xảy ra lỗi trong gcc khiến thuộc tính được căn chỉnh không hoạt động với các biến ngăn xếp. Nó dường như đã được sửa bằng bản vá được liên kết bên dưới. Liên kết bên dưới cũng chứa khá nhiều thảo luận cho vấn đề này.

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=16660

Tôi đã thử mã của bạn ở trên với hai phiên bản gcc khác nhau: 4.1.2 từ hộp RedHat 5.7 và nó không thành công tương tự như sự cố của bạn (các mảng cục bộ không có cách nào được căn chỉnh theo giới hạn byte 0x1000). Sau đó, tôi đã thử mã của bạn với gcc 4.4.6 trên RedHat 6.3 và nó hoạt động hoàn hảo (các mảng cục bộ được căn chỉnh). Myth TV folks gặp sự cố tương tự (bản vá gcc ở trên dường như đã khắc phục được):

http://code.mythtv.org/trac/ticket/6535

Dù sao, có vẻ như bạn đã tìm thấy một lỗi trong gcc, lỗi này dường như sẽ được sửa trong các phiên bản sau.


3
Theo lỗi liên kết, gcc 4.6 là bản phát hành đầu tiên với sự cố này đã được khắc phục hoàn toàn cho tất cả các kiến ​​trúc.
texthell

Bên cạnh đó, mã lắp ráp được tạo bởi gcc để tạo biến được căn chỉnh trên ngăn xếp quá khủng khiếp và không được tối ưu hóa. Vì vậy, có hợp lý khi phân bổ các biến được căn chỉnh trên ngăn xếp thay vì gọi memalign()không?
Jérôme Pouiller

13

GCC gần đây (được thử nghiệm với 4.5.2-8ubuntu4) dường như hoạt động như mong đợi với mảng được căn chỉnh đúng cách.

#include <stdio.h>

int main(void)
{
    float a[4] = { 1.0, 2.0, 3.0, 4.0 };
    float b[4] __attribute__((aligned(0x1000))) = { 1.0, 2.0, 3.0, 4.0 };
    float c[4] __attribute__((aligned(0x10000))) = { 1.0, 2.0, 3.0, 4.0 };

    printf("%p %p %p %p\n", &a[0], &a[1], &a[2], &a[3]);
    printf("%p %p %p %p\n", &b[0], &b[1], &b[2], &b[3]);
    printf("%p %p %p %p\n", &c[0], &c[1], &c[2], &c[3]);
}

Tôi có:

0x7ffffffefff0 0x7ffffffefff4 0x7ffffffefff8 0x7ffffffefffc
0x7ffffffef000 0x7ffffffef004 0x7ffffffef008 0x7ffffffef00c
0x7ffffffe0000 0x7ffffffe0004 0x7ffffffe0008 0x7ffffffe000c

Điều này hơi ngạc nhiên, khi xem xét các mảng được phân bổ trong ngăn xếp - nó có nghĩa là ngăn xếp hiện đầy lỗ?
ysap

Hoặc ngăn xếp của anh ta được căn chỉnh 16 byte.
user7116

9

Căn chỉnh không hiệu quả cho tất cả các loại. Bạn nên cân nhắc sử dụng cấu trúc để xem các thuộc tính đang hoạt động:

#include <stdio.h>

struct my_float {
        float number;
}  __attribute__((aligned(0x1000)));

struct my_float a[4] = { {1.0}, {2.0}, {3.0}, {4.0} };

int
main(void)
{
        printf("%p %p %p %p\n", &a[0], &a[1], &a[2], &a[3]);
}

Và sau đó, bạn sẽ đọc:

0x603000 0x604000 0x605000 0x606000

Đó là những gì bạn đã mong đợi.

Chỉnh sửa: Đẩy bởi @yzap và sau @Caleb Trường hợp nhận xét, vấn đề ban đầu là do phiên bản GCC chỉ . Tôi đã kiểm tra GCC 3.4.6 so với GCC 4.4.1 bằng mã nguồn của người yêu cầu:

$ ./test_orig-3.4.6
0x7fffe217d200 0x7fffe217d204 0x7fffe217d208 0x7fffe217d20c
$ ./test_orig-4.4.1
0x7fff81db9000 0x7fff81db9004 0x7fff81db9008 0x7fff81db900c

Rõ ràng là các phiên bản GCC cũ hơn (đâu đó trước 4.4.1) cho thấy các bệnh lý liên kết.

Lưu ý 1: Mã đề xuất của tôi không trả lời câu hỏi mà tôi hiểu là "căn chỉnh từng trường của mảng".

Lưu ý 2: Đưa non-static a [] vào bên trong main () và biên dịch với GCC 3.4.6 phá vỡ chỉ thị căn chỉnh của mảng struct nhưng giữ khoảng cách 0x1000 giữa các cấu trúc ... vẫn tệ! (xem câu trả lời của @zifre để biết cách giải quyết)


2
Như đã trả lời bởi zifre, đó không phải là kiểu mà là thực tế là bạn đã làm cho nó tĩnh trong phiên bản của mình.
ysap

@ysap, đó là cả phiên bản GCC và định nghĩa toàn cầu đã làm cho nó hoạt động. Cảm ơn bạn đã bình luận! Tôi đã sửa câu trả lời để sửa nó. :)
levif
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.