So, the reason why you consider to add a checksum is because you have a few unused bits in a structure? Hmm. Would you also consider to increase your structure to make room for a checksum if there was no free space leftover? If the answer is No, don't add complexity to your code only because you can.


Besides of that, any checksum would be weak anyways. So at least only perform some very basic and cheap operations. In the example below I just use bit rotation and XOR. The function supports adding information in subsequent calls as well as the definition of the bit width to be used for the checksum (e.g. to fill your whole free space even if you have an odd number of bits unused in your structure). The latter, together with the circular shift, ensures that no information ever processed is lost when we extract the checksum from the always 8-byte return value.
Code:
#include <inttypes.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>


/// @brief Calculate a basic checksum with defined bit width from any data.
///
/// The function relies on both `pData` != NULL and 0 < `useBits` <= 64.
///
/// @param pData     Pointer to any data.
/// @param dataSize  Size of the data as number of bytes.
/// @param useBits   Number of bits to be used for the resulting checksum.
/// @param initial   Initial value of the checksum.
///
/// @return The calculated checksum.
static uint64_t GetChecksum(const void *pData, size_t dataSize, unsigned useBits, uint64_t initial)
{
  const unsigned shift = useBits - 1;
  for (const unsigned char *dataIter = pData, *const end = dataIter + dataSize; dataIter < end; ++dataIter)
    initial = ((initial << 1) | ((initial >> shift) & 1)) ^ (uint64_t)*dataIter;


  return initial & (((uint64_t)1 << useBits) - 1);
}


struct foo
{
  int32_t a;
  char b; // obviously `b` has a 24-bit padding
  int64_t c;
};


int main(void)
{
  void *const raw = malloc(sizeof(struct foo));
  if (raw == NULL)
    return 1;


  struct foo *pFoo = raw;
  uint8_t *buf = raw;


  pFoo->a = 543210;
  pFoo->b = 'x';
  pFoo->c = -1234567890000;


  // The resulting checksum occupies only `patternWidth` bits in the returned
  // value. This meets the width of the unused padding in `struct foo`.
  const unsigned patternWidth = 24;


  // Members can be shuffled to obfuscate the algorithm a little.
  // In the first call, 0 or a suitable veil is passed to `initial`.
  uint64_t checksum = GetChecksum(&pFoo->b, sizeof(pFoo->b), patternWidth, 5316907);
  // The previously returned `checksum` is passed to `initial`in subsequent calls.
  checksum = GetChecksum(&pFoo->a, sizeof(pFoo->a), patternWidth, checksum);
  checksum = GetChecksum(&pFoo->c, sizeof(pFoo->c), patternWidth, checksum);
  printf("checksum: 0x%016" PRIX64 "\n\n", checksum);


  // Insert the checksum via byte array.
  buf[5] = (uint8_t)checksum;
  buf[6] = (uint8_t)(checksum >> 8);
  buf[7] = (uint8_t)(checksum >> 16);


  printf("pFoo:\n->a = %" PRIi32 "\n->b = '%c'\n->c = %" PRIi64 "\n\n", pFoo->a, pFoo->b, pFoo->c);


  const uint64_t readChecksum = (uint64_t)buf[5] | ((uint64_t)buf[6] << 8) | ((uint64_t)buf[7] << 16);
  printf("readChecksum: 0x%016" PRIX64 "\n", readChecksum);


  free(raw);
}