2017-03-05 66 views
0

首先,這個問題並不適用於那些認爲自己是C++警察的人員,因爲它涉及到一些嚴重的C彎曲以擠回一點內存,所以請用你的守護天使帽子閱讀這個問題。char和string的相同變量

我有一個程序,許多字符串分配在malloc其中大多數是1個字符長。 包含1個字符的字符串約需〜32個字節的實存儲器包括描述符,塊大小等 所以我的巧妙的計劃是使用字符*指針存儲由這個char或字符串:

char *sourceStringOrChar; 

sourceStringOrChar = malloc(10); 
//-- OR 
sourceStringOrChar = (char *)'A'; //-- Note that its '' and not "" 

if((intptr_t)sourceStringOrChar & (~0xFF)) 
    TRACE("%s\n", sourceStringOrChar); 
else 
    TRACE("%c\n", sourceStringOrChar); 

我已經知道malloc返回ENOMEM當它的內存不足,其中這個常量的實際值是12.這給了我一些希望,malloc返回結果中有一個空缺。 我已經開始閱讀malloc的源代碼,以確定這是否可行,但如果某人在這裏對mallob的深度知識有所瞭解,那可能爲我節省很多時間。

編輯:

  • 當然,問題是這是否是可能的。
  • 你們當中有些人擔心自由/ strlen的等等,但請注意,這是一個示例代碼,它可以處理同上述與

    if((intptr_t)sourceStringOrChar & (~0xFF)) 
    { 
        strlen(sourceStringOrChar); 
        free(sourceStringOrChar); 
    } 
    
  • 而且,我也不會如果沒有很大的內存問題,就進入危險的路徑

+0

什麼是這樣做的呢?你的程序是否有一些不尋常的內存限制?另外,如果有時你的指針指向一個以NUL結尾的字符串,有時它們指向一個沒有NUL終結符的'char',你的程序如何知道哪個是哪個? –

+1

你有一個內存泄漏,注意男孩! –

+3

或者......你可以爲單個角色分配一大塊內存。讓指針指向該緩衝區,並簡單地檢查該地址是否在該緩衝區的範圍內。從而消除一些駭客。 – StoryTeller

回答

0

我一直在研究CLISP的代碼。在那裏做了類似的事情,將一些立即值壓入指針(而不是從垃圾收集堆中分配一個對象)。

在CLISP的情況下,這是有效的,因爲我們知道分配器可以返回的地址範圍。然後有一點永遠不會被一個有效的地址設置,如果設置表明「指針」不是一個實際的指針,而是數據(在你的情況下,單個或更多的字符)。

btw:使用char *並不是一個好計劃,由於未定義的行爲,或者意外地將這樣的「指針」傳遞給例如strlen,您已經完成了一步。我想使用union是一個更好的方法(儘管我現在沒有時間檢查標準中是否允許以這種方式使用聯合的實際規則)。更安全的是在結構中包裝一個uintptr_t

[..]但是如果某人在這裏對malloc的深層有一些瞭解,那可能爲我節省很多時間。

這不會給你買東西。切換操作系統,平臺或只是使用的標準庫,一切都可能與您瞭解的當前正在查看的malloc實現有所不同。

一種方法是使用您自己的分配器 - 就像使用CLISP一樣 - 從某個池中獲取內存(例如,通過Linux/BSD上的mmap獲取),它駐留在某個預定義的地址處。

但是,您還可以使用malloc(或更適合的功能,例如C11 aligned_allocposix_memalign)將allocate aligned memory用於您的字符串。假設您將每個字符串對齊到一個偶數地址。這樣,當你看到一個設置了最低有效位的地址時,你可以確定它實際上不是一個地址,而是直接數據,即字符串本身。

僅使用malloc分配2個字節對​​齊的內存,所以:分配2個附加字節。如果malloc返回的地址已正確對齊,則返回下一個正確對齊的地址(char_ptr + 2),並在該地址之前直接標記單元格,並指示原始地址已對齊(char_ptr[1] = '1')。另一方面,如果返回的地址未正確對齊,則返回直接跟隨的字節(其正確對齊; char_ptr + 1),並將該單元直接標記在該地址之前(因此,第一個; char_ptr[0] = '0')。

釋放時,直接在傳入的地址之前查看單元格,它包含標記以告訴您需要哪個地址free

在代碼:

#define IS_ALIGNED_2BYTE(value) (((uintptr_t)(value) & 0x01) == 0) 

/// Allocate a memory region of the specified size at an even (2 byte 
/// aligned) address. 
/// 
/// \param[in] size Required size of the memory region. 
/// \return Pointer to the memory, or NULL on failure. 
inline static void * allocate_2byte_aligned(size_t size) { 
#ifdef HAVE_ALIGNED_ALLOC 
    return aligned_alloc(2, size); 
#elif defined(HAVE_POSIX_MEMALIGN) 
    void * ptr; 
    if (posix_memalign(&ptr, sizeof(void *), size) == 0) { 
    assert(IS_ALIGNED_2BYTE(ptr)); // Paranoia due to uncertainty 
            // about alignment parameter to 
            // posix_memalign. 
    return ptr; 
    } else { 
    return NULL; 
    } 
#else 
    char * const memory = malloc(size + 2); 
    if (! memory) { 
    return NULL; 
    } 
    if (IS_ALIGNED_2BYTE(memory)) { 
    // memory is correctly aligned, but to distinguish from originally 
    // not aligned addresses when freeing we need to have at least one 
    // byte. Thus we return the next correctly aligned address and 
    // leave a note in the byte directly preceeding that address. 
    memory[1] = '1'; 
    return &(memory[2]); 
    } else { 
    // memory is not correctly aligned. Leave a note in the first byte 
    // about this for freeing later and return the next (and correctly 
    // aligned) address. 
    memory[0] = '0'; 
    return &(memory[1]); 
    } 
#endif 
} 


/// Free memory previously allocated with allocate_2byte_aligned. 
/// 
/// \param[in] ptr Pointer to the 2 byte aligned memory region. 
inline static void free_2byte_aligned(void * ptr) { 
    assert(IS_ALIGNED_2BYTE(ptr)); 
#if defined(HAVE_ALIGNED_ALLOC) || defined(HAVE_POSIX_MEMALIGN) 
    free(ptr); 
#else 
    char const * const memory = ptr; 
    void const * original_address; 
    if (memory[-1] == '0') { 
    // malloc returned an address that was not aligned when allocating 
    // this memory block. Thus we left one byte unused and returned 
    // the address of memory[1]. Now we need to undo this addition. 
    original_address = &(memory[-1]); 
    } else { 
    // malloc returned an address that was aligned. We left two bytes 
    // unused and need to undo that now. 
    assert(memory[-1] == '1'); 
    original_address = &(memory[-2]); 
    } 
    free((void *) original_address); 
#endif 
} 

創建和銷燬「指針或即時的數據」的結構是那麼簡單:

typedef struct its_structure { 
    uintptr_t data; ///< Either a pointer to the C string, or the actual 
        ///< string, together with a bit to indicate which 
        ///< of those it is. 
} its; 

its its_alloc(size_t size) { 
    if (size < sizeof(uintptr_t)) { 
    its const immediate_string = {.data = 0x01}; 
    return immediate_string; 
    } else { 
    void * const memory = allocate_2byte_aligned(size); 
    assert(IS_ALIGNED_2BYTE(memory)); 
    its const allocated_string = { 
     .data = memory ? (uintptr_t) memory : (0x01 | 0x02) /* Invalid string */}; 
    return allocated_string; 
    } 
} 

void its_free(its string) { 
    if (IS_ALIGNED_2BYTE(string.data)) { 
    free_2byte_aligned((void *) string.data); 
    } // else immediate, thus no action neccessary 
} 

上面的代碼實際上是從a small library I wrote測試/寫這個答案。如果你想然後使用/增強它。

+0

我發現了一個可以與標準malloc協同工作的解決方案,我會在有空的時候(幾個小時)嘗試添加它。 –

+0

仍在等待您的解決方案 – Moav

+0

@Moav我沒有忘記你,對於延遲感到遺憾(雖然我生病了,然後必須處理其他東西)。 –

0

如果你真的有「很多很多的字符串」,這是一個字符長,那麼下面的至少一個必須是真實的:

  1. 你的人物都長

  2. 超過八位您可以通過"interning"您的字符串大大受益,因此您不會創建同一字符串的多個副本。

如果你的字符串是不可變的(或者你可以安排它們不會在適當位置發生變異),那麼#2是一個非常有吸引力的選項。在#1爲假並且只有255個可能的單字符字符串的情況下,您可以簡單地通過索引到510字節的預建表(交替單字符和NUL)來實習。更一般的實習策略需要一個哈希表或其他類似的東西,還有更多的工作(但潛在的非常有價值)。

如果你的「字符」實際上是短字節序列,但不經常重複,那麼你可能想要實現一個簡單的池分配方案。如果你的字符串沒有太多「流失」,這將是最簡單/最有效的;也就是說,你不經常分配並立即釋放字符串。

一個簡單的字符串池分配方案是選擇一些cutoff並將該大小的所有字符串順序分配到足夠大的塊中以容納許多短字符串。大小將取決於您的確切應用程序,但我在一個模型中取得了一些成功,其中「short」字符串最多爲31個字節,塊大小恰好爲4096個字節,始終分配在4096個字節的邊界上(使用posix_memalign , 例如)。池分配器維護一個單獨的塊,並附加新分配的字符串。它還保留一個分配的塊的列表,每個塊都有一個活動字符串的計數。 pool_malloc(n)如果字符串長於截斷點,則推遲到malloc(n);否則,如果當前塊已滿,則在首次分配新塊之後,將新的字符串放在當前活動塊的末尾。 pool_free(s)檢查s是否足夠短以適合塊。如果不是,則它僅調用free(),如果是,則在活動塊列表中找到該塊並減少其活動字符串計數。 (由於大塊都是4k對齊的,因此很容易找到塊。)在這個主題上有很多變體:您可以將元數據放入塊本身,而不是保留外部元數據;你可以簡單地釋放短字符串,直到整個池被釋放爲止(如果有大量的空閒空間,這會節省大量的時間以犧牲額外的內存使用量);可以使固定長度的字符串,這使得它更容易立即釋放一個字符串的塊

這兩種策略可以結合使用(但需要你保持某種空閒列表結構。);如果您能夠在一次操作中釋放整個實習生表格/內存池,這是非常有效的,這往往是可能的。 (參見,例如,Apache的便攜式運行的方法來分配內存。)