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.

Two copying labels retain different strings while two static labels share one reused scratch array; a separate warning distinguishes expired stack storage from valid aliasing.
Original hypothetical ownership comparison. The shared array remains alive; no deterministic output is asserted for an expired local array.

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

  1. Run the two-label shared-scratch example with both setter choices and verify whether independent snapshots are expected.
  2. Update a dedicated persistent buffer through short, long and empty strings using explicit refresh calls.
  3. Review function-local and queue-owned sources; reject every borrowed lifetime that ends before the label’s use.
  4. Exercise copied-to-borrowed and borrowed-to-copied transitions using independent storage, with allocation diagnostics enabled.
  5. 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.

Similar Posts