使用结构大小的一半增加结构指针

时间:2009-07-10 14:59:38

标签: c c89 pointer-arithmetic

我只是有一个有趣的问题要处理,我认为没有简洁的方法来解决它。

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

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;

并使用

访问“raw”节点数组
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

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

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

寻找一种不会过度投射的方式的奖励积分:)

3 个答案:

答案 0 :(得分:2)

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

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

无奖励点解决方案是将指针转换为char *以计算偏移量。您将以字节为单位调整偏移量。然后,您可以使用强制转换添加字节偏移量。为了最小化转换,您可以编写一个帮助函数,为函数指针设置正确的值。

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

你可以调用你的简化数组,复制节点,然后释放旧数组。但是,当你重新分配时,你会失去realloc优势。

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

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

答案 1 :(得分:1)

如果你不想演员:

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

答案 2 :(得分:1)

你的语法搞砸了。结构标记名称位于结构定义之前;以后的任何事情都是宣言。

无论

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));

它允许pNewBuffer->nodes[i]访问0 <= i < nMaxNodes,无需在任何地方进行投射。

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

C99引入了“灵活的阵列成员”

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

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