C parameter pass by value when parameter is a structure. Can we ensure at least the single parameter is atomic? - Stack Overflow

admin2025-05-01  0

Have a builder function (think poor man's constructor) that takes some structure values and returns another structure by value.

typedef int32_t grid_unit_t;
typedef struct grid_point_t
{
    grid_units_t  x;  // 0 - 8000
    grid_units_t  y;  // 0 - 8000
} grid_point_t;

typedef struct grid_slope_t
{
    grid_units_t  dx;  // -8000 - 8000, but never 0
    grid_units_t  dy;  // -8000 - 8000
} grid_slope_t;

typedef struct grid_line_t
{
    grid_point_t pnt[2]; // two points that define the line
    grid_units_t length; // used in pnt to line distance
    grid_slope_t m;      // stored as dy and dx m = (dy/dx)
    grid_units_t b;      // dx is never 0.
} grid_line_t;

grid_line_t build_line(grid_point_t p0, grid_point_t p1)
{
    grid_units_t dx = p1.x - p0.x == 0? 1: p1.x - p0.x;
    grid_units_t dy = p1.y - p0.y;
    return (grid_line_t){
        .pnt[0] = p0, .pnt[1] = p1,
        .m.dx = dx,
        .m.dy = dy,
        .length = sqrt(dx*dx + dy*dy),
        .b = p0.y - (p0.x * dy) / dx
    };
}

extern grid_point_t my_p0;
extern grid_point_t my_p1;
void process_line(grid_line_t *line);

void main(void)
{
    // build a line based on some external points
    grid_line_t my_line = build_line(my_p0, my_p1);
    // some random processing of line
    process_line(&my_line);
}

I know that the parameters are copied then passed into the function. I am pretty sure the two parameters are not copied atomically with respect to each other, but is there any way to ensure that each parameter itself is copied atomically?

Some processors will now copy 64bit objects atomically so I could ensure alignment then cast the structure to int64 but was looking for a language supported option.

I am not interested in debating the desirability of doing this, just looking to see if it is possible. Also this is C not C++ so no stdatomic. I also understand there are other ways of solving this with mutex and such.

Have a builder function (think poor man's constructor) that takes some structure values and returns another structure by value.

typedef int32_t grid_unit_t;
typedef struct grid_point_t
{
    grid_units_t  x;  // 0 - 8000
    grid_units_t  y;  // 0 - 8000
} grid_point_t;

typedef struct grid_slope_t
{
    grid_units_t  dx;  // -8000 - 8000, but never 0
    grid_units_t  dy;  // -8000 - 8000
} grid_slope_t;

typedef struct grid_line_t
{
    grid_point_t pnt[2]; // two points that define the line
    grid_units_t length; // used in pnt to line distance
    grid_slope_t m;      // stored as dy and dx m = (dy/dx)
    grid_units_t b;      // dx is never 0.
} grid_line_t;

grid_line_t build_line(grid_point_t p0, grid_point_t p1)
{
    grid_units_t dx = p1.x - p0.x == 0? 1: p1.x - p0.x;
    grid_units_t dy = p1.y - p0.y;
    return (grid_line_t){
        .pnt[0] = p0, .pnt[1] = p1,
        .m.dx = dx,
        .m.dy = dy,
        .length = sqrt(dx*dx + dy*dy),
        .b = p0.y - (p0.x * dy) / dx
    };
}

extern grid_point_t my_p0;
extern grid_point_t my_p1;
void process_line(grid_line_t *line);

void main(void)
{
    // build a line based on some external points
    grid_line_t my_line = build_line(my_p0, my_p1);
    // some random processing of line
    process_line(&my_line);
}

I know that the parameters are copied then passed into the function. I am pretty sure the two parameters are not copied atomically with respect to each other, but is there any way to ensure that each parameter itself is copied atomically?

Some processors will now copy 64bit objects atomically so I could ensure alignment then cast the structure to int64 but was looking for a language supported option.

I am not interested in debating the desirability of doing this, just looking to see if it is possible. Also this is C not C++ so no stdatomic. I also understand there are other ways of solving this with mutex and such.

Share Improve this question edited Jan 3 at 4:23 3CxEZiVlQ 39.6k11 gold badges84 silver badges93 bronze badges asked Jan 2 at 19:25 bd2357bd2357 7949 silver badges18 bronze badges 10
  • 3 Why not use _Atomic? (That's a C feature.) – user2357112 Commented Jan 2 at 19:33
  • 2 Some processors will now copy 64bit objects atomically so I could ensure alignment then cast the structure to int64 but was looking for a language supported option. - That would be strict-aliasing UB (without something like a typedef using GNU C __attribute__((may_alias))), and would only give you a 64-bit load in the asm if that deref didn't optimize away or get hoisted out of a loop. See Which types on a 64-bit computer are naturally atomic in gnu C and gnu C++? -- meaning they have atomic reads, and atomic writes - There are none. – Peter Cordes Commented Jan 2 at 19:33
  • 2 If you want the compiler to assume that other threads might change the object you're reading at any time, you need something like C11 _Atomic and functions from #include <stdatomic.h> (en.cppreference.com/w/c/thread#Atomic_operations), or GNU C __atomic_load_n(&my_p0, __ATOMIC_RELAXED). (gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html Which lets you do atomic operations on objects that aren't declared atomic, like C++20 std::atomic_ref.) – Peter Cordes Commented Jan 2 at 19:36
  • 2 Also, reading a global variable atomically is the problem you think you have; using the result as a function argument doesn't make a difference vs. assigning it to a local variable. – Peter Cordes Commented Jan 2 at 19:40
  • 1 Thanks all. I only recently started using a compiler that could be switch to C11, my brain is very stuck in C99 and forget the world has moved on. :) – bd2357 Commented Jan 2 at 19:47
 |  Show 5 more comments

1 Answer 1

Reset to default 3

ISO standard C has _Atomic objects since C11 (current is C23). No version of ISO C makes language- or standard-library-level provision for atomic operations on non-_Atomic objects, though some C implementations provide such as extensions. Since you seem to rule out use of synchronization objects such as mutexes, giving the external variables from which you are reading the structure values _Atomic type is what standard C has available to serve your objective:

extern _Atomic grid_point_t my_p0;
extern _Atomic grid_point_t my_p1;

The _Atomic qualifier is part of the data type, so all declarations of these objects must agree about it.

Note well that there is an immediate consequence on your code: the members of an _Atomic structure cannot be accessed without provoking undefined behavior. You should instead use whole-structure accesses. Individual members should be accessed on a non-atomic copy, either before writing (the whole structure) to the atomic object or after reading from it.

Ordinary accesses to _Atomic objects are performed atomically, with sequentially consistent memory semantics. That makes it very easy to get atomic accesses (your program wouldn't need anything else to get the guarantees you asked for), but sequentially consistent semantics are pretty strong. You can perform atomic accesses with weaker memory order semantics via functions defined in stdatomic.h. That may provide for better performance, but in your particular case it would require an extra copy of each object, which might outweigh any cost reduction from the weaker semantics. For example,

#include <stdatomic.h>

extern _Atomic grid_point_t my_p0;
extern _Atomic grid_point_t my_p1;

int main(void) {
    // build a line based on some external points

    grid_point_t p0 = atomic_load_explicit(&my_p0, memory_order_relaxed);
    grid_point_t p1 = atomic_load_explicit(&my_p1, memory_order_relaxed);

    grid_line_t my_line = build_line(p0, p1);

    // ...
}

Memory ordering is about the visibility of modifications of other objects to threads that observe the values of atomic objects. You can see surprising results if you get this wrong. If you're unsure what you need then sticking with sequential consistency (ordinary accesses or memory_order_seq_cst) is the safe play.

转载请注明原文地址:http://www.anycun.com/QandA/1746100851a91678.html