7

What are the C++98 and C++11 memory models for local arrays and interactions with threads?

I'm not referring to the C++11 thread_local keyword, which pertains to global and static variables.

Instead, I'd like to find out what is the guaranteed behavior of threads for arrays that are allocated at compile-time. By compile-time I mean "int array[100]", which is different to allocation using the new[] keyword. I do not mean static variables.

For example, let's say I have the following struct/class:

struct xyz { int array[100]; };

and the following function:

void fn(int x) {
  xyz dog;
  for(int i=0; i<100; ++i)  { dog.array[i] = x; }
  // do something else with dog.array, eg. call another function with dog as parameter
  }

Is it safe to call fn() from multiple threads? It seems that the C++ memory model is: all local non-static variables and arrays are allocated on the stack, and that each thread has its own stack. Is this true (ie. is this officially part of the standard) ?

mtall
  • 3,574
  • 15
  • 23

2 Answers2

10

Such variables are allocated on the stack, and since each thread has its own stack, it is perfectly safe to use local arrays. They are not different from e.g. local ints.

Oberon
  • 3,219
  • 15
  • 30
  • Thanks. Is this explicitly written in the C++11 standard ? – mtall Mar 05 '13 at 07:30
  • @mtall: Indirectly. All non-static variables in a function body are part of the function scope and have automatic storage duration. This also applies to functions invoked by `std::thread::thread(F&&,Args&&...)`. So you have to read four different sections if you want to puzzle everything together. – Zeta Mar 05 '13 at 07:33
  • In the end, every call to `fn` will create its own instance of `xyz`. So if two threads call `fn` at the same time, then both threads will create and modify its own instance of `xyz`, which is thread safe. – TianyuZhu Mar 05 '13 at 07:36
3

C++98 didn't say anything about threads. Programs otherwise written to C++98 but which use threads do not have a meaning that is defined by C++98. Of course, it's sensible for threading extensions to provide stable, private local variables to threads, and they usually do. But there can exist threads for which this is not the case: for instance processes created by vfork on some Unix systems, whereby the parent and child will execute in the same stack frame, since the v in vfork means not to clone the address space, and vfork does not redirect the new process to a different function, either.

In C++11, there is threading support. Local variables in separate activation chains in separate C++11 threads do not interfere. But if you go outside the language and whip out vfork or anything resembling it, then all bets are off, like before.

But here is something. C++ has closures now. What if two threads both invoke the same closure? Then you have two threads sharing the same local variables. A closure is like an object and its captured local variables are like members. If two or more threads call the same closure, then you have a de facto multi-threaded object whose members (i.e. captured lexical variables) are shared.

A thread can also simply pass the address of its local variables to another thread, thereby causing them to become shared.

Kaz
  • 55,781
  • 9
  • 100
  • 149
  • I would differentiate between capture by reference and capture by value, since capture by value won't share anything between the threads. – Zeta Mar 05 '13 at 07:31
  • `vfork()` blocks the parent process until the child either terminates or loads another executable image over itself and if the child calls any other function or returns from the current one, it would destroy the parent stack and result in undefined behaviour. I can't see how one can implement useful threading with `vfork()`. – Hristo Iliev Mar 05 '13 at 07:41
  • @HristoIliev That may be true on some system you're looking at, but not in Unix. I don't see anything here about blocking the parent: http://pubs.opengroup.org/onlinepubs/007908775/xsh/vfork.html – Kaz Mar 05 '13 at 07:47
  • @Zeta Capture by value simply isn't capture. (Well, only in languages in which everything is immutable.) If we can modify a captured variable, such that the original is untouched, it is not a captured variable. You can call it one, and even write that into a standard, just like you can call a dog's tail another leg. – Kaz Mar 05 '13 at 07:53
  • 1
    Here's one thing I didn't got: if a closure got a *local* variable by reference, and then was launched in a separate thread → so in the parent thread the function returned, and the variable no more valid — will the variable still be valid for the closure that was launched in a thread? I am asking because e.g. in Common LISP it is still valid. But as I know in C++ a local variables usually allocated in a stack, so I am not sure, how a things goes here. – Hi-Angel Dec 13 '14 at 17:08