簡體   English   中英

嚴格別名規則和glibc的strlen實現

[英]Strict aliasing rule and strlen implementation of glibc

我已經閱讀了一段時間的嚴格別名規則,我開始變得非常困惑。 首先,我已經閱讀了這些問題和一些答案:

根據它們(據我所知),使用指向另一種類型的指針訪問char緩沖區違反了嚴格別名規則。 但是, strlen()的glibc實現有這樣的代碼(刪除了注釋和64位實現):

size_t strlen(const char *str)
{
    const char *char_ptr;
    const unsigned long int *longword_ptr;
    unsigned long int longword, magic_bits, himagic, lomagic;

    for (char_ptr = str; ((unsigned long int) char_ptr 
             & (sizeof (longword) - 1)) != 0; ++char_ptr)
       if (*char_ptr == '\0')
           return char_ptr - str;

    longword_ptr = (unsigned long int *) char_ptr;

    himagic = 0x80808080L;
    lomagic = 0x01010101L;

    for (;;)
    { 
        longword = *longword_ptr++;

        if (((longword - lomagic) & himagic) != 0)
        {
            const char *cp = (const char *) (longword_ptr - 1);

            if (cp[0] == 0)
                return cp - str;
            if (cp[1] == 0)
                return cp - str + 1;
            if (cp[2] == 0)
                return cp - str + 2;
            if (cp[3] == 0)
                return cp - str + 3;
        }
    }
}

longword_ptr = (unsigned long int *) char_ptr; line顯然將unsigned long int別名為char 我不明白是什么讓這成為可能。 我看到代碼處理對齊問題,因此沒有問題,但我認為這與嚴格的別名規則無關。

第三個相關問題的接受答案是:

但是,有一個非常常見的編譯器擴展,允許您從char轉換到其他類型的正確對齊指針並訪問它們,但這是非標准的。

我想到的只有-fno-strict-aliasing選項,是這種情況嗎? 我無法在glibc實現者所依賴的任何地方找到它,並且這些注釋在某種程度上意味着這個演員表沒有任何擔心,例如很明顯沒有問題。 這讓我覺得它確實很明顯,我錯過了一些愚蠢的東西,但我的搜索失敗了。

在ISO C中,此代碼將違反嚴格別名規則。 (並且還違反了無法定義與標准庫函數同名的函數的規則)。 但是,此代碼不受ISO C規則的約束。標准庫甚至不必以類C語言實現。 該標准僅指定實現實現標准函數的行為。

在這種情況下,我們可以說實現是一個類似C的GNU方言,如果代碼是使用編寫器的預期編譯器和設置編譯的,那么它將成功實現標准庫函數。

在編寫別名規則時,標准的作者只考慮了對所有實現都有用的形式,因此應該強制使用。 C實現針對各種目的,並且標准的作者沒有試圖指定編譯器必須做什么以適合任何特定目的(例如,低級編程),或者就此而言,任何目的。

像上面那樣依賴於低級構造的代碼不應該在沒有聲稱適合低級編程的編譯器上運行。 另一方面,任何不能支持此類代碼的編譯器都應被視為不適合低級編程。 請注意,編譯器可以采用基於類型的別名假設, 如果他們合理地努力識別常見的別名模式,仍然適用於低級編程。 一些編譯器編寫者在代碼視圖中的投入非常高,既不適合普通的低級編碼模式,也不符合C標准,但編寫低級代碼的任何人都應該只是認識到那些編譯器的優化器不適合低級別使用碼。

標准的措辭實際上比實際的編譯器實現更奇怪:C標准討論了聲明的對象類型,但編譯器只能看到指向這些對象的指針。 因此,當編譯器看到從char*unsigned long* ,它必須假設char*實際上是使用聲明類型為unsigned long的對象別名,使得轉換正確。

需要注意的是:我假設strlen()被編譯成一個庫,后來只鏈接到應用程序的其余部分。 因此,優化器在編譯時沒有看到函數的使用,迫使它假設轉換為unsigned long*確實是合法的。 如果你用strlen()調用

short myString[] = {0x666f, 0x6f00, 0};
size_t length = strlen((char*)myString);    //implementation now invokes undefined behavior!

strlen()是未定義的行為,如果在編譯strlen()本身時看到你的使用,你的編譯器將被允許剝離整個strlen()體。 允許strlen()在此調用中按預期運行的唯一事實是, strlen()被單獨編譯為庫,從優化器隱藏未定義的行為,因此優化器必須假定轉換為合法的編譯strlen()

因此,假設優化器不能調用“未定義的行為”,從char*轉換為其他任何東西的原因是危險的,不是別名,而是對齊。 在某些硬件上,如果您嘗試訪問未對齊的指針,則會發生奇怪的事情。 硬件可能會從錯誤的地址加載數據,引發中斷,或者只是非常緩慢地處理請求的內存負載。 這就是為什么C標准通常聲明這種強制轉換未定義的行為。

然而,您看到有問題的代碼實際上顯式處理了對齊問題(第一個包含(unsigned long int) char_ptr & (sizeof (longword) - 1)循環)。 之后, char*被正確對齊以重新解釋為unsigned long*

當然,所有這些都不是真正符合C標准,但它符合編譯器的C實現,這個代碼是用來編譯的。 如果gcc人員修改了他們的編譯器以對這段代碼進行操作,那么glibc人就會大聲抱怨它以便gcc將被改回以正確處理這種類型的轉換。

在一天結束時,標准C庫實現必須違反嚴格的別名規則才能正常工作並提高效率。 strlen()只需要違反這些規則是有效的, malloc() / free()函數對必須能夠獲取具有聲明類型Foo的內存區域,並將其轉換為聲明類型為Bar的內存區域。 並且malloc()實現中沒有malloc()調用,它會首先為對象提供聲明的類型。 C語言的抽象簡單地在這個層面上被破壞了。

基本假設可能是函數是單獨編譯的,不適用於內聯或其他交叉函數優化。 這意味着沒有編譯時信息在函數內部或外部流動。

該函數不會嘗試通過指針修改任何內容,因此沒有沖突。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM