2017-03-05 44 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的便携式运行的方法来分配内存。)

相关问题