【问题标题】:Increase a struct pointer with half the struct size将结构指针增加一半的结构大小
【发布时间】:2009-07-10 14:59:38
【问题描述】:

我刚刚处理了一个有趣的问题,但我没有找到解决它的好方法。

我有两个表示复杂图形的基本数据结构,声明如下:

typedef struct _node_t node_t;
typedef struct _graph_t graph_t;

struct {
    /* Data fields omitted */
    node_t * pNextByLevel;
    node_t * pNextByProximity;
    node_t * pNextByRank;
} node_t;

struct {
    /* Data fields omitted */
    size_t nNodes;
    size_t nMaxNodes;
    node_t * pFirstByLevel;
    node_t * pFirstByProximity;
    node_t * pFirstByRank;
} graph_t;

实际的节点紧跟在标题之后,因此通常使用“graph_t”创建

graph_t * pNewBuffer = calloc(1, sizeof(graph_t) + nMaxNodes * sizeof(node_t));
pNewBuffer->nMaxNodes = nMaxNodes;

并且使用

访问“原始”节点数组
node_t * pNewBufferNodes = (node_t *) &pNewBuffer[1];

现在,有一个支持函数在减少节点数量的缓冲区上运行。它看起来像这样:

status_t reduce(graph_t** ppBuffer)
{
    graph_t * pReplacement, * pOld = *ppBuffer;
    size_t nRequired; 
    node_t * oldBuffer = (node_t *) &pOld[1];

    /* complex calculation ultimately computes 'nRequired' */

    pReplacement = realloc(pOld, sizeof(graph_t) + nRequired * sizeof(node_t));

    if ( pReplacement != pOld )
    {
        int i;
        node_t * newBuffer = (node_t *) &pReplacement[1];
        ptrdiff_t offset = newBuffer - oldBuffer;

        for ( i = 0; i < requiredNodes; i++ )
        {
            newBuffer[i].pFirstByLevel += offset;
            newBuffer[i].pFirstBySimilarity += offset;
            newBuffer[i].pFirstByRank += offset;
        }
        *ppBuffer = pReplacement;
    }
}

现在,这已经很好地工作了很长时间。以上任何错误都源于我是凭记忆写的,我只是想解释一下这个想法。

现在让我感到困惑的是,当使用新模块的缩减功能时,输入没有“正确”对齐。当我检查地址时,我注意到以下属性:

 ((char *) newBuffer - (char *) oldBuffer) % sizeof(graph_t) == 0
 ((size_t) newBuffer) % sizeof(node_t) == 0
 ((size_t) oldBuffer) % sizeof(node_t) == 0
 ((char *) newBuffer - (char *) oldBuffer) % sizeof(node_t) == sizeof(node_t) / 2

这当然会导致一些问题,因为“偏移”值变得不正确,但由于数据结构的所有其他使用都有效(不存在“真正的”对齐问题),所以它并不那么明显。

这归结为我的问题 - 当偏移量不能表示为元素的整数时,您是否看到了一种增加指针的简洁方法?

找到不过度施法的方法的奖励积分:)

【问题讨论】:

  • 我很困惑 oldBuffer 和 newBuffer 在转换为 sizeof(node_t) 时是 sizeof(node_t) 的倍数,但它们的差异不是倍数。通常没有理由说明任何一个缓冲区的地址应该sizeof(node_t) 的倍数 - 通常结构的对齐要求是任何成员的最大对齐要求,而不是总大小。
  • “这个功能已经很好地工作了很长时间”这一事实纯粹是运气。正如onebyone所说,2个缓冲区的地址没有理由应该是size_t(node_t)的倍数,它只需要是对齐要求的倍数。另请注意,您分配事物的方式也不能保证您的 node_t 数组,除非 graph_t 的对齐要求与 node_t 的要求相同或更严格。
  • 对我所说的稍作更正:对于分配,它被指定为与小于结构大小的任何类型的最大对齐要求对齐,它实际上不必是成员.我说“指定”而不是“保证”,因为最后我听说 linux 内核与 gcc 就实际执行此操作的责任发生了争执。但是如果 sizeof(node_t) 是 16,那么在某个特定平台上所有足够大的分配都是 16 对齐的,这一点也不令人难以置信。可能是因为分配器的工作方式,而不是因为有一个 16 字节的类型,ofc。

标签: c c89 pointer-arithmetic


【解决方案1】:

在 ptrdiff_t 上:“这是两个指针之间的减法运算返回的类型。这是一个有符号整数类型,因此可以转换为兼容的基本数据类型。两个指针的减法只允许具有指向同一数组元素的指针(或数组中刚刚超过最后一个元素的元素)的有效定义值。对于其他值,行为取决于系统特性和编译器实现。"

当你使用 realloc 时,你不是在这种情况下。所以你的偏移量不会是一个整数。这解释了你的问题。

没有奖励积分的解决方案是将您的指针转换为 char* 以计算偏移量。您最终会得到一个字节偏移量。然后,您可以使用强制转换添加字节偏移量。为了最大限度地减少强制转换,您可以编写一个帮助函数,为您的节点指针设置正确的值。

如果您想使用 realloc,我没有看到其他解决方案,因为您的初始数组已被 realloc 释放。字节偏移似乎是唯一的方法。

您可以调用减少的数组,复制节点,然后释放旧数组。但是当重新分配到位时,您将失去重新分配的优势。

其他解决方案会迫使您更改数据结构。您可以使用 malloc 独立分配节点,并且减少更简单。您只需释放不再需要的节点。这似乎是最干净的方式,但你必须重构......

我希望它有所帮助。如果我误解了,请告诉我...

【讨论】:

  • 啊,当然!您对 ptrdiff_t 问题的看法非常正确。
【解决方案2】:

如果您不想投射:

newBuffer[i].pFirstByLevel = newBuffer[i].pFirstByLevel - oldBuffer + newBuffer;            
newBuffer[i].pFirstBySimilarity = newBuffer[i].pFirstBySimilarity - oldBuffer + newBuffer;            
newBuffer[i].pFirstByRank = newBuffer[i].pFirstByRank - oldBuffer + newBuffer;

【讨论】:

    【解决方案3】:

    你的语法有问题。结构标记名称位于结构定义之前;之后的任何内容都是声明。

    要么

    typedef struct _node_t {
        /* Data fields omitted */
        node_t * pNextByLevel;
        node_t * pNextByProximity;
        node_t * pNextByRank;
    } node_t;
    

    typedef struct _graph_t graph_t;
    struct _graph_t {
        /* Data fields omitted */
        size_t nNodes;
        size_t nMaxNodes;
        node_t * pFirstByLevel;
        node_t * pFirstByProximity;
        node_t * pFirstByRank;
    };
    

    这就是你想要写的。


    这是一种比较常见的解决方法,但需要对现有代码进行一些重组。

    /* same node_t as before */
    typedef struct _node_t {...} node_t;
    /* same graph_t as before */
    typedef struct _graph_header_t {...} graph_header_t;
    /* new type */
    typedef struct _graph_t {
        graph_header_t header;
        node_t nodes[1];
    } graph_t;
    
    graph_t pNewBuffer = calloc(1, sizeof(graph_t) + (nMaxNodes-1) * sizeof(node_t));
    

    它允许对0 &lt;= i &lt; nMaxNodes 访问pNewBuffer-&gt;nodes[i],无需在任何地方进行强制转换。

    现在,如果您可以声明 node_t nodes[0],这会更好,在计算分配大小时避免非一,但即使一些编译器对此感到满意,我不相信它被标准所接受.

    C99 引入“灵活的数组成员”

    typedef struct _graph_t {
        graph_header_t header;
        node_t nodes[];
    } graph_t;
    

    这几乎是同一件事,但由实际标准定义。一些例外:灵活的数组成员只能放在结构的末尾,sizeof(pNewBuffer-&gt;nodes) 无效(尽管 GCC 返回 0)。否则,sizeof(graph_t) 等于 node_t[] 数组的大小为零。

    【讨论】:

    • 是的,我更喜欢使用灵活的 C 数组,但是这个特定的模块需要是 C89 :( 不过我不让你评论 typedef,实际上这些是在单独的文件中声明的 (头文件中的 typedefs,C 文件中的结构声明)
    • 我只是在评论你的真实代码必须是struct _foo_t {...},而不是你在这里写的struct {...} _foo_t
    猜你喜欢
    • 2013-02-23
    • 1970-01-01
    • 1970-01-01
    • 2013-04-06
    • 2017-07-28
    • 1970-01-01
    • 2017-06-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多