Подтвердить что ты не робот

Выравнивание указателя наследования C struct

Фон

Я создал основную структуру данных связанных списков, в основном для обучения. Одна из целей этого списка заключалась в том, что он может обрабатывать разные структуры данных. Поэтому я попробовал свои силы в составе структуры, чтобы имитировать "наследование" в C. Вот структуры, которые составляют основу для моего связанного списка.

typedef struct Link {
    struct Link* next;
    struct Link* prev;
} Link;

typedef Link List;

В моей реализации я решил иметь часовое node, которое служит как головой, так и хвостом списка (вот почему 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 сначала указывает на Первый 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.

TL;DR

В C-структурах a Base* может указывать на Derived, если Base является первым членом в Derived. Может ли Derived* указывать на Base, если ни один из Derived не доступен для определенных членов?

EDIT: замените соответствующие вызовы функций их эквивалентным встроенным кодом.

4b9b3361

Ответ 1

Престижность вашей презентации.

Я думаю, что ваша реализация должна работать нормально, потому что C гарантирует, что адрес структуры является адресом ее начального члена. Отложите в сторону утверждения C о выравнивании структурных элементов, эта гарантия должна означать, что, пока ваша реализация всегда ставит Link в качестве первого члена, это не должно вызывать проблемы с выравниванием.

из здесь: C99 §6.7.2.1:

13 Внутри объекта структуры члены, не являющиеся битовыми полями, и блоки в которых находятся битовые поля, имеют адреса, которые увеличиваются в порядке в котором они объявлены. Указатель на объект структуры, подходящим образом преобразуется, указывает на его начальный член (или если этот член является бит-поле, затем в блок, в котором он находится) и наоборот. В объекте структуры может быть неназванное заполнение, но не на его начало

Это должно быть то, что вы хотели сказать о Base * и Derived *, хотя в чистом C. такая вещь не существует. Это просто структуры, которые имеют одинаковый макет памяти.

Однако я думаю, что это немного хрупко реализовать, как это, потому что Node и Link напрямую зависят друг от друга. Если вы изменили структуру Node, ваш код станет недействительным. На данный момент я не вижу смысла иметь дополнительный struct Link, кроме того, что вы можете просто написать новый Node для нового типа повторного использования ссылки.

На самом деле существует связанная реализация списка, которая сразу же пришла мне в голову, когда я увидел ваш пост и работал таким образом, который очень похож на то, как вы собираетесь использовать свой список: список ядра

Он использует тот же List-element (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))

Если вы посмотрите на способ реализации макроса, вы увидите, что он предлагает итерацию над записями списка, не зная ничего о макете записей, в которых содержится список. Предполагая, что я интерпретирую ваше намерение правильно, я думайте, что это так, как вы хотели бы, чтобы это было.

Ответ 2

В C-структурах Base * может указывать на Derived, если Base является первым членом в Derived. Может ли Derived * указывать на Base, если ни один из Derived конкретных членов не доступен?

Если вы имеете в виду "Может ли Derived" указывать на произвольную базу (включая ту, которая не является первым членом производного объекта) ", тогда: Технически, нет. Мое понимание Отчет о дефектах № 74 - это требования к выравниванию могут быть разными.

Q: Если структура имеет поле типа t, может ли выравнивание требования поля отличаются от требований к выравниванию объектов одного типа, не являющихся членами структур? Если ответ на (a) есть "да", тогда, когда это применимо, остальные следует предположить, что вопросы были заданы для обоих объектов в пределах структуры и объекты вне структуры.

A: В подпункте 6.1.2.5 говорится: "... указатели на квалифицированные или неквалифицированные версии совместимых типов должны иметь одинаковое представление и требования к выравниванию. ' В подпункте 6.5.2.1 говорится:" Каждый член небитового поля структуры или объекта объединения выровнен в определенного в соответствии с его типом ". И позже," Следовательно, в структурном объекте может быть неназванное дополнение,... по мере необходимости, чтобы добиться соответствующего выравнивания ". а) это возможно для реализации для представления обобщенных требований к удовлетворяют условию 6.1.2.5. Эти требования могут быть усиление с использованием определенного для реализации поведения в подпункте 6.5.2.1. Да, требования к выравниванию могут быть отличается.

Ответ 3

Я написал это в комментариях к ответу ouah, но я представлю его как ответ сам по себе.

Это верно, поскольку ouah пишет, что код в вашем вопросе может иметь теоретическую проблему с выравниванием, если вы идете по стандарту C. Тем не менее, я не думаю, что существует одна архитектура, в которой ваш код может (или маловероятен) работать, на ABI которого есть такие шаблоны выравнивания. По крайней мере, я знаю, что я знаю, и это включает в себя опыт работы с несколькими различными настольными и встроенными архитектурами. Я также не могу представить себе архитектуру, которая могла бы получить что-либо от таких свойств выравнивания.

Также стоит отметить, что эта схема действительно используется на практике довольно часто; это похоже на то, что вы всегда можете поместить int в указатели, поскольку это не гарантируется никаким межплатформенным стандартом, но практически нет платформы, на которой это не так, и люди делают это все время.