3

I have found similar questions to the one I have (i.e. Is changing a pointer considered an atomic action in C?), but I have not been able find anything that gives me a definitive yes/no answer when it comes to writing a pointer.

My problem is thus. I have an array of struct pointers with a pointer field.

For example

struct my_struct {
    void *ptr;
};
struct my_struct *my_struct[10];

The pointer fields of each struct is read and written to by a thread.

For example

uint8_t index;
for(index = 0; index < 10; index++)
{
    if(my_struct[index]->ptr != NULL)
    {
        my_struct[index]->ptr = NULL;
    }
}

Periodically an interrupt occurs that will read (does not write and I can't use a lock because the handler is time critical) one or more of the pointers stored in my array of structs.

For example

void *ptr = my_struct[2]->ptr;

Ultimately I do not care about whether the pointer read by the interrupt handler is new or old; only that it hasn't been corrupted. Clearly, the following operation will not be atomic

my_struct[index]->ptr = NULL;

But, can I be sure that if an interrupt occurs and reads "my_struct[index]->ptr" it will not be corrupted? I suspect that this should be true since (1) a pointer should completely fit into a register (at least for my target, MSP430) and (2) writing a single register is most likely an atomic instruction. Is my reasoning correct?

HXSP1947
  • 1,311
  • 1
  • 16
  • 39
  • 4
    Nothing is atomic unless explicitly specified in the standard or your compiler documentation – M.M Jun 01 '18 at 05:31
  • It's not a matter of how wide your registers are, it's matter of how your RAM is wired and whether your compiler actually uses hardware pointers or synthesizes them. Nothing in the C standard requires a one to one mapping between pointers and address registers. – jwdonahue Jun 01 '18 at 05:43
  • Exactly which MSP430 are you using? On what board? Which compiler? – jwdonahue Jun 01 '18 at 05:51
  • I'm using gcc-msp430 to compile. I'm not sure what you mean by which MSP430 though (I'm using TelosB). – HXSP1947 Jun 01 '18 at 05:54
  • Then why do you mention MSP430 in your question and have the MSP430 tag on it? As far as I know, gcc-msp430 targets the MSP430 processor. Perhaps TelosB is the name of the board? – jwdonahue Jun 01 '18 at 05:57
  • MSP430 is the microprocessor used by TelosB (the platform) – HXSP1947 Jun 01 '18 at 05:59
  • Looking at the [TelosB](http://www.memsic.com/userfiles/files/Datasheets/WSN/telosb_datasheet.pdf), I see that it's a 16 bit RISC chip. Is `sizeof(void*)==2` true? – jwdonahue Jun 01 '18 at 06:01
  • yes, sizeof(void*)==2 is true. – HXSP1947 Jun 01 '18 at 06:03
  • [This](https://stackoverflow.com/questions/50235930/atomic-operation-in-multithreaded-embedded-software) is essentially the same question. – Lundin Jun 01 '18 at 09:23
  • 1
    Lots of quack answers so I decided to post one. Be very sceptic with these answers, they are all over the place. – Lundin Jun 01 '18 at 11:38
  • Atomic access to word size variable is almost never a problem. But correct ordering is. – curiousguy Jun 04 '18 at 01:48

4 Answers4

3

On the MSP430 architecture (and with any of the C compilers you could be using), pointer reads/writes indeed are atomic. Therefore, there is no specific std::atomic (or equivalent) support code for MSP430.

(For types larger than the native word size, the only way to make atomic accesses would be to disable interrupts.)

In your case, all you have to care about is that the compiler might not be aware of concurrent accesses, and reorders its own accesses to the variable. In other words, the assignment to ptr might be reordered (or, in extreme cases, optimized away) unless you use a volatile access. You could either make ptr itself volatile (void * volatile ptr;), or add volatile when assigning it.

CL.
  • 173,858
  • 17
  • 217
  • 259
  • 1
    "On the MSP430 architecture (and with any of the C compilers you could be using), pointer reads/writes indeed are atomic." What are you basing this on? I don't see how this could hold true. Any compiler might decide to "load pointer into register from stack", "do stuff with register". – Lundin Jun 01 '18 at 09:25
  • This question is not about writes through a pointer, but about a write to the pointer itself. – CL. Jun 01 '18 at 11:18
  • 1
    And that's the very same issue. There are no guarantees that such writes are done in a single instruction. – Lundin Jun 01 '18 at 11:22
  • 2
    This question is not about the ISO C semantics, but about what gcc actually implements. On this architecture, a write of the new pointer value is done with a single MOV instruction (and no other implementation is really possible). – CL. Jun 01 '18 at 11:37
  • 1
    In before you write `x = y[i]` or any other case of pointer arithmetic, where that reasoning falls apart completely. – Lundin Jun 01 '18 at 11:42
  • 3
    What exactly has `x = y[i]` to do with this question? What is the partial value that would be observed by the interrupt handler, `x` or `y`? – CL. Jun 01 '18 at 12:24
  • It's just an example of utterly common code which will likely not be atomic. The program will load `y` into a register, calculate the offset, in comes interrupt/context switch that changes `x`, then that change is overwritten when the program returns to the caller. – Lundin Jun 01 '18 at 13:00
  • As for this question specifically, it is sensible compiler behavior to evaluate `my_struct[index]->ptr != NULL` by checking if `ptr` is a null pointer or not. The compiler may then do something like: "load whatever value that makes something a null pointer in register", "load ptr in register", "compare ptr with register". Not atomic. Similarly, something might be made a null pointer by setting individual bits of the pointer - probably not atomic either. – Lundin Jun 01 '18 at 13:04
  • 4
    In this question, the interrupt does *not* change the pointer, only the main program. The value of `ptr` cannot be loaded twice if it is quialified with `volatile`. And on this architecture, reading/setting individual bits of a pointer is not even possible for the compiler (unless you force it to do so with explicit unaligned or byte operations). Your considerations are valid for ISO C, and for rmw operations or non-volatile accesses, but that is not what this question is about. – CL. Jun 01 '18 at 14:28
  • @curiousguy libstdc++ does not have any MSP430-specific code. – CL. Jun 04 '18 at 08:10
  • @CL. libstdc++ does not have any MSP430-specific code *because* pointer reads/writes indeed are atomic? – curiousguy Jun 04 '18 at 13:41
  • @curiousguy No, libstdc++ does not have atomic types even for types larger than 16 bits. – CL. Jun 04 '18 at 14:19
  • "_the compiler might not be aware of concurrent accesses, and reorders its own accesses to the variable_" How do you use volatile to prevent that? – curiousguy Jun 04 '18 at 18:22
  • @curiousguy I wrote that in my answer. Or see [elsewhere](https://www.google.com/search?q=c+volatile). – CL. Jun 04 '18 at 18:32
  • @CL. What is your source that volatile would prevent reordering? – curiousguy Jun 04 '18 at 22:42
  • @curiousguy [When is a Volatile Object Accessed?](https://gcc.gnu.org/onlinedocs/gcc/Volatiles.html) – CL. Jun 05 '18 at 07:33
3

TelosB uses msp430 Series 1 MCU, on which all pointers are 16-bit variables. (There are other series of msp430 where this fails to hold.) Normally, access to 16-bit variables on msp430 is atomic. However, the compiler will separate the access to the pointer in multiple instructions in case there is a reason to suspect the access could be unaligned (source: the TI forum): unlike x86, msp430 can only do word access on aligned memory.

In the example you provide above, there is no reason for the compiler to suspect that, since the pointer is part of a structure that is declared without alignment modifiers. So the short story is that you're almost certainly safe. Still, if you're asking for formal guarantees, there are probably none. Also, you should use volatile for the reasons CL mentions.

kfx
  • 8,136
  • 3
  • 28
  • 52
  • I like this answer better than my own. I did confirm the OP was getting 16 bit sizes for pointers and size_t as a quick check for any goofy compiler configuration issues, but my answer was too brief and failed to warn that in the general case, one can't make such assumptions. – jwdonahue Jun 01 '18 at 21:58
  • Please explain what volatile would do – curiousguy Jun 04 '18 at 01:42
2

The RAM for this chip is embedded on the chip. The compiler agrees with the processor on pointer size, I think you should be ok.
There's no cache, so you wouldn't even have to worry about fences for synchronizing access.

EDIT: Lots of other answers here, +1 to all of them. The OP's question is more of an MSP430/gcc compiler question than a C language question and in my haste, late last night before turning out the lights and going to bed, I gave the above answer. I have no previous experience with the MSP430, so I went online and did some spelunking, asked the OP to check a couple constants in their environment and jumped right to the conclusion that they probably don't have anything to worry about in the stated scenario. I am used to working with embedded C compilers that barely meet K&R standards, much less C99 or C11, but that experience actually predates C11, so I did not think to ask whether the _Atomic keyword was available (I should have!). So here's another go at it:

If you can declare _Atomic(void*) ptr;, by all means, do that. It will ensure the necessary alignment and generate code that writes and reads the pointer value atomically. As @Lundin points out, this is the only sure thing in C when it comes to atomicity. In the absence of _Atomic, assert(sizeof(void*) == 2) also assert(&(my_struct[index]->ptr) % 2 == 0), the later will assure the pointer value is stored in an aligned address location. If/when these assertions do not hold, you are at risk of reading a partially written pointer value due to misalignment or size of the pointer exceeding the word size of the processor. Even these assertions are no guarantee, as they only hold true for code compiled with DEBUG defined. If you feel the need to always verify these constraints, use if(expression) instead.

@CL.'s point regarding the volatile keyword should also be taken to heart, since the compiler is free to optimize and reorder accesses, it is possible that the interrupt routine might never see the real pointer value and unless that data was initialized to NULL prior any use in your code, that could be the cause of some very serious, difficult to diagnose bugs. This is an unlikely scenario on simple micros with no cache and no speculative execution pipelines, but it can't be ruled out either. So use the volatile keyword.

jwdonahue
  • 6,199
  • 2
  • 21
  • 43
  • 1
    What does the pointer size have to do with anything? The only thing that matters is if the C code gets translated to a single instruction or multiple ones. – Lundin Jun 01 '18 at 09:27
  • @Lundin, I partially agree with you, except that the OP is not concerned with the full range of concurrency issues, they only care whether a read can be corrupted by a partial write of the data. Given they are not tweaking the alignment for this CPU/Compiler combination and the fact they most likely don't have anything like stdatomic.h available to them, I did a quick check that they weren't using some kind of non-standard/extended addressing scheme and too quickly concluded that they were fine. I still think they are, but I'll update my answer. – jwdonahue Jun 01 '18 at 21:50
  • Can you describe how volatile should be used? – curiousguy Jun 04 '18 at 01:47
1

Since we are talking about C, most of the posted answers are nonsense. It doesn't matter how large a pointer type is and that MPC430 is a 16 bit MCU just like the 16 bit address bus. Thinking that this somehow matters for making C code atomic is plain naive.

When you write C, any variable (or a pointer) could be stored anywhere: in registers, on the stack or optimized away entirely. The programmer has no control over this, which is a good thing.

Whenever you have a chance that a variable is stored on the stack, you also have a chance for multiple instructions. Given a = b, then either a, b or both could be stored anywhere, if at all. If any of them are stored on the stack, you have a big chance that the generated machine code will result in something like:

  • Instruction 1: "store data from stack inside register"
  • Instruction 2: "do something with register".

This scenario is enough to break atomicity - the CPU hardware is irrelevant. Even in the case where the core supports writing directly from the stack to other memory, there is no guarantee that this is done in a single instruction. Period.

And even if you can verify with disassembly that the machine code is atomic, that's not necessarily a stable state of affairs. Make changes to the code, add more variables, link again, and suddenly code that was atomic before is atomic no longer, or vice versa.

The only existing guarantees for atomicity in the C language are:

  • The _Atomic keyword from C11.
  • Inline assembler where you write everything manually.
  • Using protection mechanisms such as mutex, semaphores, interrupt disable etc.
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 2
    These are are all good points, but not specifically relevant to the OP's stated question. They want to know if the pointer value written/read from RAM can be corrupted. On this chip/OS/compiler, provided alignment is not tweaked by the code, they are fine. I'll enhance my admittedly weak late night answer. – jwdonahue Jun 01 '18 at 21:39