LVGL Label Buffers: Choose Copying or Static Text Without Dangling References
Use copied label text when the producer will reuse or release its buffer. Use static text only when the application can keep the referenced bytes valid and stable for every label reader. The word static in the API means noncopying storage; it does not extend a local variable’s lifetime or make shared scratch space safe.
Applicability: this article pins LVGL v9.3.0. The proposed fixture uses a serialized UI owner, synchronous software drawing, no outstanding draw work during updates and LV_LABEL_LONG_MODE_CLIP unless stated otherwise. Asynchronous renderers require additional reader-lifetime qualification. The tests are not executed, and this is a label-buffer contract rather than a general object-deletion guide.

1. Choose an ownership model before a setter
The 9.3 label guide distinguishes lv_label_set_text, which copies text into label-managed storage, from lv_label_set_text_static, which directly uses the supplied buffer. Formatted text has a label-managed allocation too. Select by producer lifetime and update behavior rather than assuming the noncopying call is always faster.
| Producer storage | Appropriate starting choice | Required check |
|---|---|---|
| Function-local formatting array | Copying setter | Array remains valid through the call |
| Scratch array reused for several labels | Copy separately for each label | Each label retains its own snapshot |
| Immutable long-lived string | Static setter may fit | Selected label mode does not write it |
| Dedicated persistent mutable array | Static setter may fit | Capacity, serialization and refresh contract |
| Buffer owned by an external message | Copy before message release | Do not retain borrowed queue storage |
Record the owner, capacity, last possible reader and replacement rule for each noncopying label. An application-owned heap buffer can satisfy the contract just as a global array can; the important property is lifetime, not the C storage-class spelling.
2. Demonstrate the shared-scratch failure without invalid memory
Consider a hypothetical 32-byte scratch array. Format “Pump A: ready,” assign it to label A, then reuse the array for “Pump B: fault” and assign label B. If both assignments use the static setter, both labels refer to the same live array. Label A has not retained an independent snapshot.
Repeat with the copying setter for both assignments. After each successful copy, the producer may reuse its scratch bytes. The intended observations are distinct retained strings for A and B. Test the stored content as well as the rendered image; an unchanged screen region can temporarily conceal that its referenced bytes have changed.
The stack variant is a lifetime defect rather than a shared-content feature: a static setter must not retain a pointer to a local array that disappears when the producer returns. Flag that pattern during review. If demonstrating it under host memory diagnostics, isolate the invalid example; do not define a predictable displayed string after undefined behavior.
3. Make persistent-buffer updates complete transactions
For a rapidly changing value, allocate a dedicated buffer with capacity for the largest supported formatted result and terminator. Format under the UI ownership discipline, check truncation and produce a complete terminated string before asking the label to refresh. A producer thread should deliver a value or owned message, not overwrite bytes while the renderer reads them.
In the selected non-DOTS fixture, call lv_label_set_text_static again with the same explicit valid pointer after changing its content. This communicates the update to LVGL so text dimensions and drawing are refreshed. A raw memory store alone is not a complete widget update.
Test both growth and shrinkage, including an empty string. Compare the resulting bytes, measured label content and redraw. Do not leave a stale suffix after a shorter value by omitting the new terminator. Keep the buffer assigned to only its intended label unless shared content is a deliberate design.
4. Check mode-dependent writes and alias transitions
The tagged label API names LV_LABEL_LONG_MODE_DOTS. The label guide warns that its ellipsis processing edits the text in place. Therefore, do not combine borrowed read-only strings with that mode. A const-qualified pointer does not make the underlying storage writable.
For this fixture, keep borrowed mutable values in CLIP mode. If DOTS is required, start with the copying setter so LVGL owns writable text, then separately test the desired mode. Do not blindly replace borrowed bytes while an earlier ellipsis edit still has saved restoration state.
The exact implementation also makes setter transitions worth reviewing. The static setter frees a previous label-owned buffer before installing the supplied pointer. Passing the label’s own current owned text pointer back as static storage therefore risks retaining freed storage. Use an independent long-lived buffer for that transition.
Likewise, do not pass an interior pointer into the label’s current owned allocation to the copying setter. The ordinary replacement path releases the old allocation before copying the new source. Prepare an independent source snapshot instead of relying on overlapping ownership.
5. Avoid an ambiguous null refresh
The static setter’s header describes null as a refresh request, but in the tagged implementation it first frees existing nonstatic label-owned text. Thus a null call is not a safe universal refresh operation across both ownership modes. Keep this article’s borrowed-buffer update sequence explicit: pass its valid non-null pointer.
Do not use setter names as a substitute for tracking the current mode. A label may start with copied default text and later switch to borrowed storage. The destructor frees text only when the internal static flag is clear; application-owned static storage remains the application’s responsibility.
For a transition back to copied text, keep the independent source valid during the copying call and retire the old borrowed buffer only after all relevant readers are finished. The synchronous fixture simplifies that boundary; a product with queued drawing needs an actual completion/ownership proof.
6. Keep a small ownership regression
- Run the two-label shared-scratch example with both setter choices and verify whether independent snapshots are expected.
- Update a dedicated persistent buffer through short, long and empty strings using explicit refresh calls.
- Review function-local and queue-owned sources; reject every borrowed lifetime that ends before the label’s use.
- Exercise copied-to-borrowed and borrowed-to-copied transitions using independent storage, with allocation diagnostics enabled.
- Check read-only strings only in nonmutating modes, and test DOTS separately with writable owned text.
Retain the buffer ownership table, setter sequence and mode configuration. Acceptance requires both correct content and correct lifetime. A label that happens to render correctly once after its source was reused is evidence of an incomplete test, not a valid noncopying design.