Another option, if a somewhat heavy-handed one, is to put all the variables into one .c file, and expose only accessor functions. This is equivalent to the OOP practice of private variables, private helper functions, and public getters/setters.
Probably not be worthwhile on a small or embedded project, but then again, embedded CPUs are awfully powerful these days; a Cortex M0 at 160MHz doing the job of a PIC at 8MHz say? Lots of room for verbosity.
If you have atomic access to worry about (the getters/setters may sometimes be called from interrupts?), you can set an atomic flag that another function is currently executing, and decide to pass on execution (the function would return an error code that it failed). It would be very convenient to simply say, "go back to that other function and finish it up, then come back here" -- but that is actually terribly difficult to set up (such capability is one of the distinguishing features of a proper multitasking OS), so for ordinary C code it is much easier to fail out.
Or you can do atomic buffering operations, queuing the getting/setting operations so they are always done exactly in sequence. But, such a getter needs a target pointer, or callback function, to know what to do with its returned data; and that operation in turn probably needs to be performed atomically, too. So it's not that much easier, and still just as prone to bugs.
If you have a simple, special-case, one-way data flow, that doesn't necessarily need to be in sequence (like a graphical frame buffer, which is new every time): double buffering is an excellent way to handle it. Set an atomic flag which indicates which buffer to read from, and write to the opposite buffer. Toggle the flag once a read and write cycle has been completed. That way, no reading or writing is ever trampled.
Tim