C结构继承指针对齐
C struct inheritance pointer alignment
背景
我创建了一个基本的链表数据结构,主要是为了学习。该列表的一个目标是它可以处理不同的数据结构。因此,我尝试了结构组合以在 C 中模拟 "inheritance"。以下是构成我的链表基础的结构。
typedef struct Link {
struct Link* next;
struct Link* prev;
} Link;
typedef Link List;
在我的实现中,我选择了一个哨兵节点作为列表的头部和尾部(这就是 Link == List 的原因)。
要使列表真正处理数据,结构只需包含 Link 结构作为第一个成员:
typedef struct {
Link link;
float data;
} Node;
所以链表是这样的。
┌───┬───┬───┐ ┌───┬───┐ ┌───┬───┬───┐
... <--->│ P │ N │ D │<--->│ P │ N │<--->│ P │ N │ D │<---> ...
└───┴───┴───┘ └───┴───┘ └───┴───┴───┘
End Node myList First Node
List myList;
Node node1 = {{}, 1.024};
....
Node nodeN = {{}, 3.14};
list_init(&myList) // myList.next = &myList; myList.prev = &myList;
list_append(&myList, &node1);
....
list_append(&myList, &nodeN);
问题
要遍历此列表,Node
指针最初指向 第一个节点。然后它沿着列表遍历,直到它再次指向哨兵然后停止。
void traverse()
{
Node* ptr;
for(ptr = myList.next; ptr != &myList; ptr = ptr->link.next)
{
printf("%f ", ptr->data);
}
}
我的问题是 ptr != &myList
行。 这条线是否存在指针对齐问题?
for 循环正确地产生了警告:(warning: assignment from incompatible pointer type
和 warning: comparison of distinct pointer types lacks a cast
)可以通过执行它所说的并强制转换为 Node*
来消除警告。但是,这是 DumbThingToDo™ 吗?我从不访问 ptr->data
当它指向 &myList
因为循环终止一次 ptr == &myList
.
TLDR
在 C 结构中,如果 Base
是 Derived
中的第一个成员,则 Base*
可以指向 Derived
。如果访问 Derived
个特定成员中的 none 个,Derived*
能否指向 Base
?
EDIT:用等效的内联代码替换了相关的函数调用。
In C structs, a Base* can point to a Derived if Base is the first member in Derived. Can a Derived* point to a Base if none of Derived specific members are accessed?
如果您的意思是 "can a Derived* point to an arbitrary Base (including one which is not a first member of a Derived object)",那么:从技术上讲,不是。我对 Defect Report #74 的理解是对齐要求可能不同。
Q: If a structure has a field of type t, can the alignment
requirements of the field be different from the alignment requirements
of objects of the same type that are not members of structures? If the
answer to (a) is ``yes,'' then where applicable the remaining
questions should be assumed to have been asked for both objects within
structures and objects outside structures.
A: Subclause 6.1.2.5 says, '... pointers to qualified or unqualified
versions of compatible types shall have the same representation and
alignment requirements.' Subclause 6.5.2.1 says, `Each
non-bit-field member of a structure or union object is aligned in an
implementation-defined manner appropriate to its type.' And later,
'There may therefore be unnamed padding within a structure object,
... as necessary to achieve the appropriate alignment.' a) It is
possible for an implementation to state generalized requirements to
satisfy sublause 6.1.2.5. These requirements may be further
strengthened using the implementation-defined behavior made available
in subclause 6.5.2.1. Yes, the alignment requirements can be
different.
我在 ouah 的回答的评论中写了这个,但我会把它作为一个单独的答案来呈现。
正如 ouah 所写,如果您按照 C 标准,您问题中的代码 理论上可能 存在对齐问题。但是,我认为您的代码可能(或不太可能)运行 的单一架构的 ABI 表现出此类对齐模式。我至少知道 none,这包括一些不同的桌面和嵌入式架构的经验。我也无法真正想象一种架构会从这种对齐属性中获得任何好处。
同样值得注意的是,这种模式在实践中确实使用得非常普遍;这有点像你总是可以在指针中放入 int
s 的事实,因为它不受任何跨平台标准的保证,但实际上没有哪个平台是不正确的,而且人们一直都在这样做.
感谢您的演示。
我认为您的实现应该可以正常工作,因为 C 保证结构的地址是其初始成员的地址。抛开 C 关于结构成员对齐的声明,这种保证应该意味着,只要您的实现始终将 Link 作为第一个成员,这就不会导致对齐问题。
来自 here:C99 §6.7.2.1:
13 Within a structure object, the non-bit-field members and the units
in which bit-fields reside have addresses that increase in the order
in which they are declared. A pointer to a structure object, suitably
converted, points to its initial member (or if that member is a
bit-field, then to the unit in which it resides), and vice versa.
There may be unnamed padding within a structure object, but not at its
beginning
这应该是您要说的关于 Base *
和 Derived *
的意思,尽管在纯 C 中不存在这样的东西。它们只是碰巧具有相同内存布局的结构。
不过我觉得这样实现有点脆弱,因为Node和Link直接相互依赖。如果您要更改 Node 的结构,您的代码将变得无效。目前我看不出有额外的 struct Link
有什么意义,除了你可以为新类型重用 Link.
编写一个新节点
当我看到你的 post 时,我立即想到了一个链表实现,它的工作方式与你打算使用列表的方式非常相似:kernel list
它使用相同的列表元素 (list_head
):
struct list_head {
struct list_head *next, *prev;
};
它包含这个函数宏:
#define list_for_each_entry(pos, head, member) \
for (pos = list_first_entry(head, typeof(*pos), member); \
&pos->member != (head); \
pos = list_next_entry(pos, member))
如果您查看宏的实现方式,您会发现它提供了对列表条目的迭代,而对列表中包含的条目的布局一无所知。假设我正确理解了您的意图,我认为这就是您希望的方式。
背景
我创建了一个基本的链表数据结构,主要是为了学习。该列表的一个目标是它可以处理不同的数据结构。因此,我尝试了结构组合以在 C 中模拟 "inheritance"。以下是构成我的链表基础的结构。
typedef struct Link {
struct Link* next;
struct Link* prev;
} Link;
typedef Link List;
在我的实现中,我选择了一个哨兵节点作为列表的头部和尾部(这就是 Link == List 的原因)。
要使列表真正处理数据,结构只需包含 Link 结构作为第一个成员:
typedef struct {
Link link;
float data;
} Node;
所以链表是这样的。
┌───┬───┬───┐ ┌───┬───┐ ┌───┬───┬───┐
... <--->│ P │ N │ D │<--->│ P │ N │<--->│ P │ N │ D │<---> ...
└───┴───┴───┘ └───┴───┘ └───┴───┴───┘
End Node myList First Node
List myList;
Node node1 = {{}, 1.024};
....
Node nodeN = {{}, 3.14};
list_init(&myList) // myList.next = &myList; myList.prev = &myList;
list_append(&myList, &node1);
....
list_append(&myList, &nodeN);
问题
要遍历此列表,Node
指针最初指向 第一个节点。然后它沿着列表遍历,直到它再次指向哨兵然后停止。
void traverse()
{
Node* ptr;
for(ptr = myList.next; ptr != &myList; ptr = ptr->link.next)
{
printf("%f ", ptr->data);
}
}
我的问题是 ptr != &myList
行。 这条线是否存在指针对齐问题?
for 循环正确地产生了警告:(warning: assignment from incompatible pointer type
和 warning: comparison of distinct pointer types lacks a cast
)可以通过执行它所说的并强制转换为 Node*
来消除警告。但是,这是 DumbThingToDo™ 吗?我从不访问 ptr->data
当它指向 &myList
因为循环终止一次 ptr == &myList
.
TLDR
在 C 结构中,如果 Base
是 Derived
中的第一个成员,则 Base*
可以指向 Derived
。如果访问 Derived
个特定成员中的 none 个,Derived*
能否指向 Base
?
EDIT:用等效的内联代码替换了相关的函数调用。
In C structs, a Base* can point to a Derived if Base is the first member in Derived. Can a Derived* point to a Base if none of Derived specific members are accessed?
如果您的意思是 "can a Derived* point to an arbitrary Base (including one which is not a first member of a Derived object)",那么:从技术上讲,不是。我对 Defect Report #74 的理解是对齐要求可能不同。
Q: If a structure has a field of type t, can the alignment requirements of the field be different from the alignment requirements of objects of the same type that are not members of structures? If the answer to (a) is ``yes,'' then where applicable the remaining questions should be assumed to have been asked for both objects within structures and objects outside structures.
A: Subclause 6.1.2.5 says, '... pointers to qualified or unqualified versions of compatible types shall have the same representation and alignment requirements.' Subclause 6.5.2.1 says, `Each non-bit-field member of a structure or union object is aligned in an implementation-defined manner appropriate to its type.' And later, 'There may therefore be unnamed padding within a structure object, ... as necessary to achieve the appropriate alignment.' a) It is possible for an implementation to state generalized requirements to satisfy sublause 6.1.2.5. These requirements may be further strengthened using the implementation-defined behavior made available in subclause 6.5.2.1. Yes, the alignment requirements can be different.
我在 ouah 的回答的评论中写了这个,但我会把它作为一个单独的答案来呈现。
正如 ouah 所写,如果您按照 C 标准,您问题中的代码 理论上可能 存在对齐问题。但是,我认为您的代码可能(或不太可能)运行 的单一架构的 ABI 表现出此类对齐模式。我至少知道 none,这包括一些不同的桌面和嵌入式架构的经验。我也无法真正想象一种架构会从这种对齐属性中获得任何好处。
同样值得注意的是,这种模式在实践中确实使用得非常普遍;这有点像你总是可以在指针中放入 int
s 的事实,因为它不受任何跨平台标准的保证,但实际上没有哪个平台是不正确的,而且人们一直都在这样做.
感谢您的演示。
我认为您的实现应该可以正常工作,因为 C 保证结构的地址是其初始成员的地址。抛开 C 关于结构成员对齐的声明,这种保证应该意味着,只要您的实现始终将 Link 作为第一个成员,这就不会导致对齐问题。
来自 here:C99 §6.7.2.1:
13 Within a structure object, the non-bit-field members and the units in which bit-fields reside have addresses that increase in the order in which they are declared. A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning
这应该是您要说的关于 Base *
和 Derived *
的意思,尽管在纯 C 中不存在这样的东西。它们只是碰巧具有相同内存布局的结构。
不过我觉得这样实现有点脆弱,因为Node和Link直接相互依赖。如果您要更改 Node 的结构,您的代码将变得无效。目前我看不出有额外的 struct Link
有什么意义,除了你可以为新类型重用 Link.
当我看到你的 post 时,我立即想到了一个链表实现,它的工作方式与你打算使用列表的方式非常相似:kernel list
它使用相同的列表元素 (list_head
):
struct list_head {
struct list_head *next, *prev;
};
它包含这个函数宏:
#define list_for_each_entry(pos, head, member) \
for (pos = list_first_entry(head, typeof(*pos), member); \
&pos->member != (head); \
pos = list_next_entry(pos, member))
如果您查看宏的实现方式,您会发现它提供了对列表条目的迭代,而对列表中包含的条目的布局一无所知。假设我正确理解了您的意图,我认为这就是您希望的方式。