I would like to share an example:
GNU C++ library basic_string.h
[notice: as someone points out that this is a "C++" example, it may not be fit for this topic of "C". I write a "C" code, which has same concept as the example. At least, GNU gcc compiler doesn't complain anything.]
It uses [-1] to move pointer back from user string to management information block. As it alloc memory once with enough room.
Said
"
*  This approach has the enormous advantage that a string object
*  requires only one allocation.  All the ugliness is confined
*  within a single %pair of inline functions, which each compile to
*  a single @a add instruction: _Rep::_M_data(), and
*  string::_M_rep(); and the allocation function which gets a
*  block of raw bytes and with room enough and constructs a _Rep
*  object at the front.
"
Source code:
https://gcc.gnu.org/onlinedocs/gcc-10.3.0/libstdc++/api/a00332_source.html
   struct _Rep_base
   {
     size_type               _M_length;
     size_type               _M_capacity;
     _Atomic_word            _M_refcount;
   };
   struct _Rep : _Rep_base
   {
      ...
   }
  _Rep*
   _M_rep() const _GLIBCXX_NOEXCEPT
   { return &((reinterpret_cast<_Rep*> (_M_data()))[-1]); }
It explained:
*  A string looks like this:
*
*  @code
*                                        [_Rep]
*                                        _M_length
*   [basic_string<char_type>]            _M_capacity
*   _M_dataplus                          _M_refcount
*   _M_p ---------------->               unnamed array of char_type
*  @endcode
*
*  Where the _M_p points to the first character in the string, and
*  you cast it to a pointer-to-_Rep and subtract 1 to get a
*  pointer to the header.
*
*  This approach has the enormous advantage that a string object
*  requires only one allocation.  All the ugliness is confined
*  within a single %pair of inline functions, which each compile to
*  a single @a add instruction: _Rep::_M_data(), and
*  string::_M_rep(); and the allocation function which gets a
*  block of raw bytes and with room enough and constructs a _Rep
*  object at the front.
*
*  The reason you want _M_data pointing to the character %array and
*  not the _Rep is so that the debugger can see the string
*  contents. (Probably we should add a non-inline member to get
*  the _Rep for the debugger to use, so users can check the actual
*  string length.)
*
*  Note that the _Rep object is a POD so that you can have a
*  static <em>empty string</em> _Rep object already @a constructed before
*  static constructors have run.  The reference-count encoding is
*  chosen so that a 0 indicates one reference, so you never try to
*  destroy the empty-string _Rep object.
*
*  All but the last paragraph is considered pretty conventional
*  for a C++ string implementation.
// use the concept before, to write a sample C code
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
typedef struct HEAD {
    int f1;
    int f2;
}S_HEAD;
int main(int argc, char* argv[]) {
    int sz = sizeof(S_HEAD) + 20;
    S_HEAD* ha = (S_HEAD*)malloc(sz);
    if (ha == NULL)
      return -1;
    printf("&ha=0x%x\n", ha);
    memset(ha, 0, sz);
    ha[0].f1 = 100;
    ha[0].f2 = 200;
    // move to user data, can be converted to any type
    ha++;
    printf("&ha=0x%x\n", ha);
    *(int*)ha = 399;
    printf("head.f1=%i head.f2=%i user data=%i\n", ha[-1].f1, ha[-1].f2, *(int*)ha);
    --ha;
    printf("&ha=0x%x\n", ha);
    free(ha);
    return 0;
}
$ gcc c1.c -o c1.o -w
(no warning)
$ ./c1.o 
&ha=0x13ec010
&ha=0x13ec018
head.f1=100 head.f2=200 user data=399
&ha=0x13ec010
The library author uses it. May it be helpful.