so.very.tired
so.very.tired

Reputation: 3086

Is it safe to send an address of a parameter?

Consider the following code snippet:

void read_write(unsigned* ptr, bool r_or_w) {
    if (r_or_w) {
        *ptr = read_from_device_register();
    } else {
        write_to_device_register(*ptr);
    }
}

unsigned read(void) {
    unsigned data = 0;
    read_write(&data, 1);
    return data;
}

void write(unsigned data) {
    read_write(&data, 0); // <--- sending address of received argument
}

Specifically, consider the fact that void write(unsigned data) sends a pointer to a value that it has received as argument.

Comparing to:

void write(unsigned w_data) {
    unsigned data = w_data;
    read_write(&data, 0); // <--- sending address of local variable
}

In both cases data is stored on the stack, only that in the first case data is stored on the call-frame of the caller, rather than in the callee as a local variable. Is it safe? are there any pitfalls? How common is it, if at all?

Upvotes: 1

Views: 236

Answers (4)

4386427
4386427

Reputation: 44329

It's absolutely safe.

Even if read_write(&data, 0); used the pointer to change the value of data that change never impacts anything outside void write(unsigned data){..}. The unsigned data inside the function acts just as a local function variable except that it gets initialized by the value of the argument used when the function is called.

This is a simple but important feature in C: Arguments are always passed by value. A function can't change the value of the original argument. If the argument is a pointer, the function can change the value of the pointed-to-object but not the value of the argument itself.

Example:

void f1(int* p)
{
    *p = 42;
}

void f2(int n)
{
    printf("At start of f2 n is %d\n", n);
    f1(&n);
    printf("At end of f2 n is %d\n", n);
}

void f3(int* p)
{
    printf("At start of f3 *p is %d\n", *p);
    f2(*p);               
    printf("At end of f2 *p is %d\n", *p);
}

Calling the above chain like

int x = 17;
printf("Before f3 x is %d\n", x);
f3(&x);                          
printf("After f3 x is %d\n", x);

will produce (comments are mine):

Before f3 x is 17
At start of f3 *p is 17
At start of f2 n is 17
At end of f2 n is 42     // notice: f1 changed the value of n inside f2
At end of f3 *p is 17    // but when f2 returns the value of the original x is the same
After f3 x is 17         // Reason: x and n are to different variables

Upvotes: 1

nanofarad
nanofarad

Reputation: 41281

There's no issue with lifetime here, as long as read_write doesn't store the pointer somewhere where it can outlive the pointee data from the write frame. As such, this is safe from the standpoint of program correctness. In fact, there's no difference between the two options as given -- an incoming parameter's address can be taken just like the address of a local variable can be taken. As for concerns about modifying variables in the frame of write's caller, that's also irrelevant - data was passed by value.

However, pointer indirection for an unsigned integer can be a significant overhead if the function is called often or in a tight loop.

Further, from a design standpoint, this seems like an awkward design which is a bit hard to read; write delegates to read_write which ultimately delegates to write_to_device_register. This is a bit awkward to intuitively follow, and needlessly mixes the read and write paths together just to have them diverge again; this might be unsafe in the sense that the code is more brittle and more error prone to maintain as compared to a simpler design.

Upvotes: 3

Jon Reeves
Jon Reeves

Reputation: 2586

only that in the first case data is stored on the call-frame of the caller, rather than in the callee as a local variable.

Actually no, the way you've declared w_data, it will be copied by value into the function. At the point that write() is executing, it "owns" w_data in the sense that w_data is part of its scope. In fact with most ABIs, the first 3 or 4 arguments to a function are passed in hardware registers, not on the stack. In order to pass a memory location to read_write(), the compiler will have to first create an entry in the function's stack frame, store the value of the register in this entry, and pass the address of that entry to read_write().

Is it safe? are there any pitfalls?

Yes it's safe. There are always pitfalls with passing addresses to automatic variables, but these are all wrapped up in having a complete understanding of the scope of the value and how it's going to be used. For example imagine that the read_write() function was implemented using a DMA to the "device_register". This could be an asynchronous call, in which case the w_data variable would go out of scope before the operation finished. The effects would be indeterminate.

How common is it, if at all?

Extremely common in low level software applications.

Upvotes: 3

Barmar
Barmar

Reputation: 781721

The language specification doesn't say where parameters are stored; what you described about which stack frame holds the address is an implementation detail that's not part of the language. C doesn't even specify that there's a stack, although it's the typical implementation.

The location of the variable is not relevant in answering this question, just the lifetime of the variable. The lifetime of a function parameter is the same as local variables in the function's top-level block. You can pass the address to another function, and it's valid as long as it's only used before write() has returned. If the address is saved in some other data structure, using it after write() returns causes undefined behavior.

So there's no difference between using &w_data and &data as far as your question is concerned. A good compiler optimizer might even generate the same code for both versions.

Upvotes: 1

Related Questions