2009-09-23 73 views
1

這讓我堅持了幾天。如果我聲明它爲static,我無法獲得一個數組來對齊到16。__attribute__((__aligned__))不能與靜態變量一起工作

任何幫助非常感謝。

修訂版:

#include <stdio.h> 
#include <assert.h> 

#define MAX_INPUTS 250 

int main() 
{ 
float input[MAX_INPUTS] __attribute__ ((__aligned__(16))); 
printf("Address of input: %p\n", input); 

printf("Assert1: %x\n", (((int) (input))  )  ); 
printf("Assert2: %x\n", (((int) (input)) % 16)  ); 
printf("Assert3: %x\n", (((int) (input)) % 16) == 0 ); 

assert ( (((int) (input))  )  ); 
assert ( (((int) (input)) % 16)  ); /* Fails */ 
assert ( (((int) (input)) % 16) == 0 ); /* Passes */ 

return 0; 
} 

的輸出是:

Address of input: 0022FB70 
Assert1: 22fb70 
Assert2: 0 
Assert3: 1 
Assertion failed: (((int) (input)) % 16), file aligntest.c, line 16 

正如人們所期望的,斷言2,因爲在地址0結束失敗然而,利用:

static float input[MAX_INPUTS] __attribute__ ((__aligned__(16))); 

的輸出是:

Address of input: 00404028 
Assert1: 404028 
Assert2: 8 
Assert3: 1 
Assertion failed: (((int) (input)) % 16), file aligntest.c, line 16 

斷言2仍然失敗,但結果不爲零。當Assert2被註釋掉時,Assert3通過(有或沒有靜態聲明)並且程序正常終止。

我在運行XP Pro的Intel Core 2 Duo上使用MinGw gcc 4.4.0。

+0

@Ian,檢查出的除了@ PMG的答案 - 我想你在那裏可以找到足夠好的問題(以及解決方案,即引入揮發性物質以阻止gcc對其優化的瘋狂追求)。 – paxdiablo 2009-09-24 13:19:57

+0

@Ian,重新更新:由於相同的原因,assert2將會*總是*失敗。assert3將*總是*傳遞(無論地址是否實際上由鏈接器對齊)。 GCC *認爲*它將會對齊,並會拋出assert1/3,並可能將assert2優化爲assert(0)。 – paxdiablo 2009-09-24 13:42:58

+0

查看我的(第二個)答案的最新更新 – pmg 2009-09-25 12:02:49

回答

2

在我工作的機器上(Windows Vista,MinGW gcc 4.3.2),您的代碼在任何優化級別都沒有生成任何assert的彙編器!

爲了得到要生成的斷言,我不得不想出volatile int變量並編譯-O0標誌。

int main(void) { 
    float input[MAX_INPUTS] __attribute__ ((__aligned__(16))); 
    static float input_static[MAX_INPUTS] __attribute__ ((__aligned__(16))); 
    volatile int addr_as_int; 

    printf("Address of input: %p\n", &input); 
    addr_as_int = (int)input; 
    print_pointer(input); 
    print_int(addr_as_int); 
    printf("normal int: %08x; int%%16: %02x\n", addr_as_int, addr_as_int%16); 
    printf("Assert: %d\n", (addr_as_int % 16) == 0); 
    assert((addr_as_int % 16) == 0); /* Passes */ 

    printf("Address of input_static: %p\n", &input_static); 
    addr_as_int = (int)input_static; 
    print_pointer(input_static); 
    print_int(addr_as_int); 
    printf("static int: %08x; int%%16: %02x\n", addr_as_int, (addr_as_int)%16); 
    printf("Assert: %d\n", (addr_as_int % 16) == 0); 
    assert((addr_as_int % 16) == 0); /* Does not Pass */ 

    return 0; 
} 

我不知道爲什麼編譯器選擇從對象文件中刪除斷言。我快速谷歌搜索沒有透露任何有趣的。

更新1(@Pax在@Falaina的建議添加 - 我們都建議你接受這一個,如果它原來是如此):

其實我覺得@Falaina在已經釘它評論@帕克斯的回答:

只是一個建議。你是否在編譯優化?編譯器可能會嘗試變得聰明,並且「嘿,這個變量對齊到16個字節,顯然地址是%16」,並用1替換所有的檢查。只是一個想法。

下面是解釋。 GCC從源代碼中確定輸入確實(應該是)對齊到16個字節。它足夠聰明,完全可以刪除assert,只需打印1就可以得到printf

然而,在鏈路級,所述接頭是不能保證對準到16個字節,而不是選擇爲8,因爲(來自@Pax):

注意,對齊屬性的有效性可被限制通過鏈接器的固有限制。在很多系統上,鏈接器只能將變量排列成一定的最大對齊。 (對於某些連接器,支持的最大對齊可能非常小)。如果鏈接器只能對齊最多8個字節對齊的變量,則在__attribute__中指定對齊(16)仍然只能爲您提供8字節對齊。有關更多信息,請參閱鏈接器文檔。

到時候把assert和非優化的printf回到代碼中已經太遲了。所以實際的可執行文件不會斷言(因爲它們已被取出),它將打印優化的1而不是計算它的運行時間。

volatile修復它在我的答案是因爲GCC不會優化包含易失組件的表達式。它將離開assert並在運行時正確計算printf參數。


您可以手動調整您的數組,如果你不介意的聲明比stricly需要大一點:

#include <assert.h> 
#include <stdio.h> 

#define MAX_INPUTS 250 

void *force_align(void *base, size_t s, int align) { 
    size_t x; 
    int k = 0; 
    x = (size_t)base; 
    while ((k < align/(int)s) && (x % align)) { 
    k++; 
    x += s; 
    } 
    if (k == align) return NULL; 
#if 0 
    printf("%d elements 'discarded'\n", k); 
#endif 
    return (void*)((size_t)base + k*s); 
} 

int main(void) { 
    #define ALIGNMENT_REQ 16 
    #define EXTRA_ALIGN_REQ (ALIGNMENT_REQ/sizeof (float)) 
    static float misaligned_input[MAX_INPUTS + EXTRA_ALIGN_REQ] 
     __attribute__ ((__aligned__(ALIGNMENT_REQ))); 
    float *input; 

    /* manual alignment, check for NULL */ 
    assert((input = force_align(misaligned_input, sizeof *input, ALIGNMENT_REQ))); 

    printf("Address of misaligned input: %p\n", misaligned_input); 
    printf("Address of input: %p\n", input); 
    printf("Assert1: %x\n", (((int) (input))    )  ); 
    printf("Assert2: %x\n", (((int) (input)) % ALIGNMENT_REQ)  ); 
    printf("Assert3: %x\n", (((int) (input)) % ALIGNMENT_REQ) == 0); 
    assert ((((int) (input))    )  ); 
#if 0 
    assert ((((int) (input)) % ALIGNMENT_REQ)  ); /* Fails */ 
#endif 
    assert ((((int) (input)) % ALIGNMENT_REQ) == 0); /* Passes */ 

    return 0; 
} 
+0

@ pmg,這個答案和一個@Falaina對我的回答發表了評論,你們已經明確了它的含義,你需要volatile的原因 - 沒有它,gcc認爲它是對齊的(16)並優化斷言(並且只打印1)。但是鏈接器沒有儘管現在改變目標文件已經太遲了,請看看我的更新你的血腥細節的答案。 – paxdiablo 2009-09-24 13:20:57

+0

嗯,我沒有爲我的測試程序進行任何優化編譯,但我一定會想要,所以這是有道理的。 我覺得很難相信鏈接器會對齊自動變量,但不是靜態的。至少,不是故意的。我試圖找出它支持的對齊方式,但被刪除了。 這讓我想知道是否有一些系統特定的編譯設置我錯過了。我會試着看看在另一臺PC上編譯時會發生什麼。 – 2009-09-24 21:23:55

+1

@Ian Shaw - 鏈接器不負責自動變量的對齊。編譯器可以生成彙編,以便爲堆棧變量調整對齊方式(例如,如果我按位分配堆棧指針,並且在分配堆棧之前用-16指定堆棧指針,我已經確定了對齊方式),編譯器對全局變量的保持不變(並且實現了靜態作爲一天結束時的全局變量),它所能做的最好的事情是告訴鏈接器它想爲變量確定一個對齊方式 – Falaina 2009-09-24 22:32:32

0

我想不出有什麼好的理由,但是在&frac12;一個小時你沒有得到任何其他的答案要麼,所以我會用一種風格的投訴和一些猜測和懷疑去...

  • 請把&出地址表達式的第一printf()。雖然(&array)(array)在C中具有相同的值,但在兩行寫入時卻似乎有些錯誤。有一天,只要關心程序中的小東西就可以節省您的錯誤,或者有人會將操作數的類型更改爲額外的&生成雙重間接指針。無論如何,風格很重要。我無法證明這一點,但我確信它的確如此。

  • 讓事情變得非常簡單。通過傳遞不可能斷言的static存儲類專注於故障情況。創建一個完全沒有任何內容的新目錄,僅複製失敗案例程序,然後根據CLI嘗試使用./whatever.\whatever運行它。驗證沒有運行,直到你編譯它。確定你正在運行你認爲你自己的東西。

  • 讓我們確切地知道你在XP上使用哪個GNU環境,有幾個。

1

here

注意,對齊屬性的有效性可以通過你的連接器固有的侷限性所限制。在很多系統上,鏈接器只能將變量排列成一定的最大對齊。 (對於某些連接器,支持的最大對齊可能非常小)。如果鏈接器只能對齊最多8個字節對齊的變量,則在__attribute__中指定對齊(16)仍然只能爲您提供8字節對齊。有關更多信息,請參閱鏈接器文檔。

我知道爲什麼斷言是不會發生,這是因爲表達的是真實的 - 不知道確切爲什麼表達是真實的,但你應該把它分解在這樣的情況下。將這些添加到您的調試聲明中:

printf("Assert1: %x\n", (((int) (input)))); 
printf("Assert2: %x\n", (((int) (input)) % 16)); 
printf("Assert3: %x\n", (((int) (input)) % 16) == 0); 

並向我們顯示結果。

此外,請檢查您正在運行的gcc版本 - 4.3.1和更早版本似乎有一個problem與對齊方式。 Cygwin似乎同時擁有gcc3和gcc4軟件包,假設您正在使用的是 - 如果不是,仍然檢查版本。

更新1:其實我認爲@Falaina已將它釘在下面的評論中。這是一個合理的解釋。

GCC從源代碼中確定輸入確實(應該是)對齊到16個字節。它足夠聰明,完全放棄斷言,併爲printf打印出1。

但是,在鏈接階段,鏈接器(不如GCC的能力)不能保證對齊到16個字節,而是選擇8(參見上面的我的報價)。到那時,將斷言和非優化的printf返回到代碼中爲時已晚。所以實際的可執行文件不會斷言(因爲它們已被取出),它將打印優化的1而不是計算它的運行時間。

揮發性修復它在@ pmg答案中的原因是因爲GCC將而不是優化包含易失組分的表達式。它將斷言保留並在運行時正確計算打印參數。

如果事實證明是這樣,這毫無疑問是我見過的更狡猾的問題之一。我毫不猶豫地把它稱爲​​一個bug,因爲gcc和ld都像廣告一樣行事 - 這是各種因素的結合。

+0

Assert1:404028 Assert2:8 我使用MinGW的,而剛纔從升級: 海合會(GCC)4.3.0 20080305(阿爾法測試)的MinGW-20080502到 GCC( GCC)4.4.0 但無濟於事。 – 2009-09-24 10:02:12

+0

你得到8爲assert2和1爲原始斷言仍然?請確認,因爲這是錯誤的。請添加斷言3並重新檢查。 – paxdiablo 2009-09-24 10:47:01

+0

Assert2失敗,即使printf報告8.

 Address of input: 00404028 Assert1: 404028 Assert2: 8 Assert3: 1 Assertion failed: (((int) (input)) % 16), file aligntest.c, line 25 
Assert3通過(當我註釋掉Assert2) – 2009-09-24 11:25:26

0

pointer to array of floatint的轉換不一定有意義。 如果pointer to array of floats大於int,則會丟失一些信息,這些信息可能是指針的「低位位」。

試試這個:

#include <stdio.h> 
#include <string.h> 

void print_pointer(void *ptr) { 
    unsigned char data[sizeof (void*)]; 
    size_t k; 

    memmove(data, &ptr, sizeof (void*)); 
    printf("ptr: "); 
    for (k=0; k<sizeof (void*); k++) { 
    printf(" %02x", data[k]); 
    } 
    puts(""); 
} 

做同樣的一個int

void print_int(int value) { /* ... */ } 

,並比較你的發現。

+0

我得到相同的輸出爲這兩個函數 ptr:28 40 40 00 – 2009-09-24 10:01:30