Skip to content

Instantly share code, notes, and snippets.

@M1cha
Last active July 8, 2022 08:30
Show Gist options
  • Save M1cha/0f1fac7c37477444c8773fc07ca5f1c7 to your computer and use it in GitHub Desktop.
Save M1cha/0f1fac7c37477444c8773fc07ca5f1c7 to your computer and use it in GitHub Desktop.
safe(r) C

Safe(r) C

Copy structs

If possible, use the assignment operator instead of memcpy:

struct s *s1;
struct s *s2;

*s1 = *s2;

Reasons

  • it's typesafe and you'll get an error if the types of the two variables don't match
  • you can't accidentally copy the wrong amount of data

Limitations

  • can't be used on un-aligned data
  • can't be used if you expect data after the struct (e.g. through a zero-sized array)

Performance

  • Usually equivalent, in some cases either slower or faster depending on the compiler

Initialize structs

If possible, use struct assignments to initialize them instead of initializing them one by one.

*s = (struct s){
    .field1 = 5,
    .field2 = 6,
};

Alternative without compound literals

struct s init_value = {
    .field1 = 5,
    .field2 = 6,
};
*s = s;

Reasons

  • All fields are guaranteed to be initialized, missing ones will be zeroed
  • less copy-pasted and more readable code since you don't have to repeat the variable name for every field

Zero-init structs

Use a zero-assignment instead of memset

*s1 = (struct s){ 0 };

Alternative without compound literals

struct s init_value = { 0 };
*s1 = init_value;

Reasons

  • you can't make mistakes when specifying the size

Static assertions

If possible, use static assertions to express safety-related assumptions.

static uint8_t buf[CONFIG_BUF_SIZE];

BUILD_ASSERT(sizeof(buf) >= 4);
void my_code(void) {
    buf[3] = 42;
}

Reasons

  • enforces that your assumptions are correct
  • documents which assumptions you made

Portable alternative

The best one I've seen so far is the one used in Zephyr RTOS

#define _DO_CONCAT(x, y) x ## y
#define _CONCAT(x, y) _DO_CONCAT(x, y)

#define BUILD_ASSERT(EXPR, MSG...) \
	enum _CONCAT(__build_assert_enum, __COUNTER__) { \
		_CONCAT(__build_assert, __COUNTER__) = 1 / !!(EXPR) \
	}

Use void for functions without arguments

void mycode(void) {}

Reasons

  • Without the void you can pass any or no arguments to that function without ever getting any warnings
  • if the header uses () and the implementation uses (int a) you also don't get warnings which causes unsafe behavior
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment