Análise do código-fonte LVGL (1): Implementação da lista encadeada lv_ll

No LVGL, é inevitável o uso de uma lista encadeada: os objetos do grupo precisam ser armazenados com uma lista encadeada, para que o foco do objeto possa ser trocado; outro exemplo é o cronômetro dentro do LVGL, e vários cronômetros também são armazenados com uma lista encadeada. Este artigo analisará o código-fonte da lista encadeada em LVGL.

1 Estrutura de lista encadeada

Para uma lista vinculada, deve haver um ponteiro inicial e um ponteiro final. Em LVGL, a estrutura de dados da lista vinculada é a seguinte:

/** Dummy type to make handling easier*/
typedef uint8_t lv_ll_node_t;

/** Description of a linked list*/
typedef struct {
    uint32_t n_size;
    lv_ll_node_t * head;
    lv_ll_node_t * tail;
} lv_ll_t;

Pode-se ver que os ponteiros de cabeça e cauda realmente usam um uint8_t *ponteiro para armazenar o endereço de determinados dados.

2 Insira a análise do código-fonte do elemento

Vamos pegar como exemplo a inserção de elementos no final da lista encadeada para analisar o código-fonte:

2.1 Função de inicialização

void _lv_ll_init(lv_ll_t * ll_p, uint32_t node_size)
{
    ll_p->head = NULL;
    ll_p->tail = NULL;
    
    /*Round the size up to 4*/
    node_size = (node_size + 3) & (~0x3);

    ll_p->n_size = node_size;
}

A função de inicialização é inicializar um único nodetamanho na lista encadeada, e aqui o comprimento é alinhado a quatro bytes.

2.2 Inserindo elementos

_lv_ll_ins_headUsado para inserir um nó na frente da lista vinculada e _lv_ll_ins_tailusado para inserir um nó no final da lista vinculada. A implementação deles é basicamente a mesma, aqui está _lv_ll_ins_tailum exemplo para análise.

#define LL_NODE_META_SIZE (sizeof(lv_ll_node_t *) + sizeof(lv_ll_node_t *))

void * _lv_ll_ins_tail(lv_ll_t * ll_p)
{
    lv_ll_node_t * n_new;

    n_new = lv_mem_alloc(ll_p->n_size + LL_NODE_META_SIZE);

    if(n_new != NULL) {
        node_set_next(ll_p, n_new, NULL);       /*No next after the new tail*/
        node_set_prev(ll_p, n_new, ll_p->tail); /*The prev. before new is the old tail*/
        if(ll_p->tail != NULL) {                /*If there is old tail then the new comes after it*/
            node_set_next(ll_p, ll_p->tail, n_new);
        }

        ll_p->tail = n_new;      /*Set the new tail in the dsc.*/
        if(ll_p->head == NULL) { /*If there is no head (1. node) set the head too*/
            ll_p->head = n_new;
        }
    }

    return n_new;
}

A primeira é alocar uma memória com tamanho ll_p->n_size + LL_NODE_META_SIZEde size, que é o tamanho de cada nó que acabamos de definir, e depois adicionar dois ponteiros para salvar o elemento anterior e o próximo elemento.

Em seguida, pegue node_set_preva função como exemplo para ver o que o código faz:

#define LL_PREV_P_OFFSET(ll_p) (ll_p->n_size)

static void node_set_prev(lv_ll_t * ll_p, lv_ll_node_t * act, lv_ll_node_t * prev)
{
    if(act == NULL) return; /*Can't set the prev node of `NULL`*/

    uint8_t * act8 = (uint8_t *)act;

    act8 += LL_PREV_P_OFFSET(ll_p);

    lv_ll_node_t ** act_node_p = (lv_ll_node_t **) act8;
    lv_ll_node_t ** prev_node_p = (lv_ll_node_t **) &prev;

    *act_node_p = *prev_node_p;
}

Em primeiro lugar act8 += LL_PREV_P_OFFSET(ll_p), é realmente a localização do actponteiro preve, em seguida, atribua o valor apontado pelo ponteiro ao ponteiro no parâmetro prev. Para node_set_next, a operação concluída é semelhante, que é alterar o valor do ponteiro actem next.

Portanto, para as duas linhas de código a seguir, o nó recém-criado prevaponta para o último elemento da lista vinculada atual e apontará nextpara NULL, para que um elemento seja inserido no final da lista vinculada.

node_set_next(ll_p, n_new, NULL);
node_set_prev(ll_p, n_new, ll_p->tail); 

Continue analisando o código:

if(ll_p->tail != NULL) {                /*If there is old tail then the new comes after it*/
	node_set_next(ll_p, ll_p->tail, n_new);
}

ll_p->tail = n_new;      /*Set the new tail in the dsc.*/
if(ll_p->head == NULL) { /*If there is no head (1. node) set the head too*/
	ll_p->head = n_new;
}

Isso significa que, para ll_pa estrutura, sabemos que apenas o tamanho de um único elemento é salvo na frente, assim como os ponteiros de cabeça e cauda. Portanto, primeiro julgue se o ponteiro da cauda da lista vinculada atual tem um valor e, em caso afirmativo, aponte-o nextpara o nó recém-criado. Em seguida, atribua o ponteiro de cauda na lista encadeada como o endereço do novo nó. Se o cabeçalho da lista vinculada também estiver vazio, isso significa que a lista vinculada acabou de ser criada e o nó não é apenas o nó principal, mas também o nó final.

2.3 Uso de inserir elementos

Com a análise acima do código-fonte para inserir elementos no final da lista encadeada, vamos dar uma olhada em como a função é realmente usada _lv_ll_ins_tail.

lv_obj_t ** next = _lv_ll_ins_tail(&ll);
LV_ASSERT_MALLOC(next);
*next = next_node;

No código-fonte anterior, sabemos que a memória do novo elemento inserido está _lv_ll_ins_tailalocada em , então inserimos primeiro e depois julgamos que, se a alocação de memória for bem-sucedida, podemos atribuir o valor do ponteiro inserido ao final como nosso nó next_node.

3 resumo

Na verdade, a implementação da lista encadeada em LVGL é semelhante à nossa estrutura de dados de lista encadeada esperada. A única diferença é que o tamanho de cada nó pode ser personalizado e, em seguida, os dados são salvos diretamente no nó em vez do ponteiro.Esta é também uma maneira de pensar. É claro que a operação da lista encadeada não se limita a inserir elementos no final, lv_ll.ctambém existem funções no arquivo para obter o comprimento da lista encadeada e excluir nós. O objetivo deste artigo é entender a estrutura de dados da lista vinculada em LVGL e, a seguir, usar o exemplo de inserção de elementos no final para aprofundar o entendimento da lista vinculada implementada em LVGL.

おすすめ

転載: blog.csdn.net/tilblackout/article/details/131143174