C - 双指针为 NULL。链表结构

C - Double pointer is NULL. Linked list structure

我大约一周前开始学习 C。为了稍微测试一下我对指针的理解,我一直在尝试创建一个链表类型结构,它表示您可能在任何 GUI 中找到的菜单栏。

Like this.

我没有解释我想出的那种结构,而是决定通过粗略的图表来表示它会更容易...(希望这里没有错误)。

所以我想出了下面的C代码。它所做的只是创建一个菜单结构。它应该循环遍历整个菜单并将其输出为文本形式...

#include <stdlib.h>
#include <stdarg.h>

typedef enum menu_item_type {
    ITEM,
    MENU,
    TERMINATOR
} menu_item_type;

typedef struct menu_item menu_item;
typedef struct menu_item {
    const char* label; // label text for item shown to user
    menu_item_type type; // item type. see above enum for possibilities
    menu_item** next; // pointer to next item
    menu_item* below; // item below in same menu    
} menu_item;

void menubar(const char* menubar_label, ...);
menu_item* menu(const char* label, ...);
menu_item* menu_va(const char* label, va_list args);
menu_item* item(const char* label);
menu_item* terminator();

/*
    setup menubar. variadic function.
*/
void menubar(const char* menubar_label, ...) {
    va_list args;
    va_start(args, menubar_label);  
    menu_item* menubar = menu_va(menubar_label, args); // menubar is a menu
    va_end(args);
    
    menu_item* item = menubar;
    menu_item* next;
    int ident = 0;
    do {
        if(item->type == TERMINATOR) {
            ident--; // unindent after submenu finishes
        }else{
            for(int i = 0; i < ident; i++) {
                printf("  "); // ident each submenu further
            }
            printf("[%s]\n", item->label); // print item label
        }
        if(item->type == MENU) {
            ident++; // add ident when submenu starts
        }
        next = *item->next;
        free(item); // item printed. no longer needed.
        item = next;
    } while(item);
}

/*
    single menu/submenu. overload variadic function for menu_va.
*/
menu_item* menu(const char* label, ...) {
    va_list args;
    va_start(args, label);
    menu_item* menu = menu_va(label, args);
    va_end(args);
    return menu;
}

/*
    create a menu/submenu with a set label. pass items in a va_list.
*/
menu_item* menu_va(const char* label, va_list args) {
    // head item is the item that starts a menu off (e.g item 'File')...
    menu_item* head = item(label); 
    head->type = MENU;
    menu_item* i = head;
    menu_item* next;
    while(i->type != TERMINATOR) {
        next = va_arg(args, menu_item*);
        if(i != head) {
            i->below = next; // next item is item below unless it's head item.
        }
        i->next = &next; // set next item
        i = next;
    }
    // next item after terminator is item below head item
    i->next = &head->below; 
    return head;
}

/*
    create item with label.
*/
menu_item* item(const char* label) {
    menu_item* i = malloc(sizeof(menu_item));
    i->label = label;
    i->type = ITEM;
    i->next = NULL;
    i->below = NULL;
    return i;
}

/*
    create terminator item. signals end of a menu.
*/
menu_item* terminator() {
    menu_item* i = item(NULL);
    i->type = TERMINATOR;
    return i;
}

int main() {
    // menubar structure is...
    menubar("Menubar Test",
        menu("File",
            item("New"),
            item("Save"),
            item("Save As..."),
            menu("Export To...",
                item("PDF File"),
                item("JPG File"),
                terminator()
            ),
            terminator()
        ),
        menu("Edit",
            item("Copy"),
            item("Paste"),
            menu("Remove...", 
                item("All"),
                item("Selection"),
                terminator()
            ),
            item("Undo"),
            terminator()
        ),
        menu("Help",
            item("About"),
            terminator()
        ),
        terminator()
    );
    return 0;
}

从这段代码我想要这样的输出:

[Menubar Test]
    [File]
        [New]
        [Save]
        [Save As...]
        [Export To...]
            [PDF File]
            [JPG File]
    [Edit]
        [Copy]
        [Paste]
        [Remove...]
            [All]
            [Selection]
        [Undo]
    [Help]
        [About]

相反,我只得到这个:

[Menubar Test]

只打印第一项。我发现第一项的 ->next 指针是 NULL 但我真的不知道为什么。我有点以为我理解指针,但显然我遗漏了一些东西。这不是我知道如何调试的真正问题。尝试调试它会产生奇怪的结果。有时我什至发现指针地址会由于未知原因而随机变化。 (printf("%p"))。大多数时候我只是以段错误结束。

如果有人能给我指出正确的方向,我将不胜感激。

我会继续回答我自己的问题。正如@John3136 所说,如果您在函数之外使用它,则设置指向局部变量的指针将不起作用。它会导致未定义的行为,这解释了为什么我原来的问题的症状如此荒谬。在我看来,这似乎是一个新手错误,但我很高兴我现在知道得更多了。

鉴于此,我修改了我的代码,现在它运行良好。我不会 post 我更新的代码,因为它不是通用的,所以可能不会很有用。