Dereferencing an uninitialized pointer to pass into sizeof()
Asked Answered
L

2

6

In a recent post, I realised that when allocating a structure variable, passing the dereferenced pointer deemed a better practice in contrast to passing the structure type to sizeof(). This is basically because the former is more resilient to code changes than the latter.

Which suggests, that in the following code method 1 is deemed a better practice than method 2.

typedef struct X_ {
    int x;
    int y;
    int z;
} X;

int main() {
    X* obj1 = malloc(sizeof(*obj1)); // ----> method 1
    X* obj2 = malloc(sizeof(X));     // ----> method 2
    return 0;
}

The question is, how valid is it to dereference obj1 in method 1 ? Inside malloc, obj1 is still unconstructed/uninitialized memory which suggests that dereferencing of obj1 happening inside sizeof() shouldn't be valid.

Let me make a guess what makes method 1 valid. Is this because since sizeof() is a compile time operation dereferencing obj1 gets translated into method 2 by the compiler?

Could someone please elaborate the technical validity of this by referring to the relevant C standards?

Liberia answered 21/7, 2020 at 12:54 Comment(3)
You are not dereferencing anything ... sizeof does not evaluate its operand (except for VLAs), ie sizeof (42/0) does not attempt to divide by zero :)Raychel
@Raychel I'm a bit confused by "you are not dereferencing anything"...I understand sizeof does not evaluate its operand except in the case of VLAs...but still, if sizeof(*obj1) is not dereferencing obj1 (which is a pointer to X) to get X, then what do we call this? Is it because this is a compile-time operation that it isn't considered "dereferencing"?Atwater
@dwillis77: sizeof only cares about the type. The type of *obj is the same whatever the value of obj is (even if obj has an invalid/indeterminate/trap value). The type of int/int (42/0) is int even if the value has no meaning :-)Raychel
S
7

The sizeof expression where the operand is not a variable length array is a non evaluated expression. So this expression

sizeof(*obj1)

is well-formed.

From the C Standard (6.5.3.4 The sizeof and alignof operators)

2 The sizeof operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type. The size is determined from the type of the operand. The result is an integer. If the type of the operand is a variable length array type, the operand is evaluated; otherwise, the operand is not evaluated and the result is an integer constant

As for your question relative to the best method of specifying an argument of malloc

X* obj1 = malloc(sizeof(*obj1)); // ----> method 1
X* obj2 = malloc(sizeof(X));     // ----> method 2

then if the type X is visible in the point of using malloc like in this case

X* obj1 = malloc(sizeof(*obj1)); // ----> method 1

then this approach is preferable.

However if the type is not visible like for example

obj1 = malloc(sizeof(*obj1)); // ----> method 1

then I prefer explicitly to specify the type like

obj1 = malloc(sizeof( X ));

Otherwise for example this code snippet

p = malloc( *p );
q = malloc( *q );

does not give enough information for the reader of the code. And the reader will need to scroll the source code forward and backward to find the declarations of p and q to determine their types.

Spinner answered 21/7, 2020 at 12:59 Comment(3)
It is arguable that the same benefits obtained with obj1 = malloc(sizeof(*obj1)) can be had with obj1 = (X*)malloc(sizeof( X ));Nitrosyl
@Nitrosyl only the first version is invariant to changes of obj1's type. If you change the type, then (type)malloc(sizeof type) requires editing 2 additional places in the code, whereas malloc(sizeof *object) does not (and will always be correct). You gain absolutely nothing by casting the result of malloc.Choking
@FelixG: if you have decent warnings enabled (-Wall -Werror), you get a type mismatch. The cast could come from a macro: #define new_object(type) ((type *)malloc(sizeof(type))). Local coding conventions could include something like this and the alternative obj1 = malloc(sizeof(*obj1)) is not available if you pass the object directly as a function argument: append_node(new_object(Node))Nitrosyl
D
2

The question is, how valid is it to dereference obj1 in method 1?

It's 100% valid. You could use it without parenthesis though, sizeof *obj1.

From N1570 ISO/IEC 9899:201x §6.5.3.4 The sizeof and _Alignof operators

2 - The sizeof operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type. The size is determined from the type of the operand. The result is an integer. If the type of the operand is a variable length array type, the operand is evaluated; otherwise, the operand is not evaluated and the result is an integer constant.

In fact one can say it's a preferable method, the reason being that if you change, for some reason, the type of the object it's easy to forget to also change the sizeof argument, using the derefenced pointer will avoid this potencial silent error.

Douglas answered 21/7, 2020 at 13:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.