|
|
Log in / Subscribe / Register

Handling argc==0 in the kernel

Handling argc==0 in the kernel

Posted Jan 29, 2022 10:39 UTC (Sat) by larkey (guest, #104463)
In reply to: Handling argc==0 in the kernel by matthias
Parent article: Handling argc==0 in the kernel

> However one is not allowed to dereference it.

But there's no dereferencing? But more plainly perhaps:

for (char **arg = argv + 1; arg < argv+argv; arg++)


to post comments

Handling argc==0 in the kernel

Posted Jan 29, 2022 11:05 UTC (Sat) by matthias (subscriber, #94967) [Link] (12 responses)

argv[1] is clearly dereferencing and the & operator then takes the address of the dereferenced value. If you have an invalid pointer, you are not allowed to dereference it. Writing &*a is UB if a is an invalid pointer, even if the combination of & and * should be a no-op. And writing &argv[1] is UB if argv is an array of length 1 (only containing the terminating NULL) as writing argv[1] is already UB. You cannot make the UB of argv[1] be defined again by later taking the address.

I agree that the compiler will probably generate the exact same code for **arg = &argv[1] and **arg = argv + 1. But in the first case, the compiler is allowed to conclude that the length of the argv array is at least two (including the final NULL value), as argv[1] is UB if the array has length one. I am not saying that any compiler actually does this, but when it comes to UB, compilers are known to do very crazy things, so it is not entirely impossible. And in the case of the main function it is even less likely that the compiler will do anything crazy. The pointer is provided by the OS and the compiler does not know the length of the array (unless it uses the special connection between argv and argc). So the code should probably be fine. But this is one of the things I really do not like about C. The threat of UB is almost everywhere. And at some point in the future some clever compiler will exploit this against you.

Handling argc==0 in the kernel

Posted Jan 29, 2022 11:22 UTC (Sat) by mchapman (subscriber, #66589) [Link]

> Writing &*a is UB if a is an invalid pointer, even if the combination of & and * should be a no-op.

No, this is not correct.

C specifies that &*a is equivalent to a: "neither [the * operator] nor the & operator is evaluated and the result is as if both were omitted". This expression is valid for any value of a, even a null pointer.

Handling argc==0 in the kernel

Posted Jan 29, 2022 23:08 UTC (Sat) by areilly (guest, #87829) [Link] (10 responses)

"And writing &argv[1] is UB if argv is an array of length 1 (only containing the terminating NULL) as writing argv[1] is already UB."

No: &argv[1] is semantically identical to argv + 1. It is pointer arithmetic and does not cause a dereference.

However: in deference to C support on AS400 (and Unisys? anything with fancy pointer representations) it *is* undefined behaviour for arrays of length zero. It's only legal to construct a pointer value to the single element past the end of the array, whether or not the pointer is subsequently dereferenced. This is also why it's technically illegal to use a post-decremented pointer traversal that accesses the first element of an array. The one-past-the-end rule doesn't extend to one-before-the-beginning. Sigh.

Handling argc==0 in the kernel

Posted Jan 30, 2022 10:43 UTC (Sun) by larkey (guest, #104463) [Link] (9 responses)

> However: in deference to C support on AS400 (and Unisys? anything with fancy pointer representations) it *is* undefined behaviour for arrays of length zero.

Aren't zero-length arrays a GNU extension? Or do you refer to flexible struct members?

Handling argc==0 in the kernel

Posted Jan 30, 2022 11:23 UTC (Sun) by areilly (guest, #87829) [Link] (1 responses)

To be honest, I was thinking about the argc==0 example here, where the argument vector allocation was real, because it was just the first part before an explicit null and the environment variable elements. In that case I don't think that the one-past-the-end situation arises, because the memory object is the whole thing and clearly not zero length. OK, I don't think that the zero-length issue is a thing, sorry.

Handling argc==0 in the kernel

Posted Jan 31, 2022 9:50 UTC (Mon) by larkey (guest, #104463) [Link]

Ah, I see, I was just curious if I missed something :) No problem!

Handling argc==0 in the kernel

Posted Jan 30, 2022 20:21 UTC (Sun) by NYKevin (subscriber, #129325) [Link] (6 responses)

It is legal to call malloc(0), and it is legal for malloc(0) to return a value other than NULL. If it does so, then the only thing you're allowed to do with that value is pass it to free(). In particular, you cannot add one to it, because it's functionally equivalent to a zero-length array (but I don't know if the standard actually uses the exact phrase "zero-length array").

In practice, most implementations either return NULL or convert it into a call to malloc(1). But even then, that's a one-byte array, not a one-object array, so you still can't legally add one to it unless the pointer's type has sizeof() == 1.

Handling argc==0 in the kernel

Posted Jan 30, 2022 20:23 UTC (Sun) by NYKevin (subscriber, #129325) [Link]

> then the only thing you're allowed to do with that value is pass it to free().

Or realloc(), I suppose. Point is, you definitely can't dereference it.

malloc(0)

Posted Jan 31, 2022 1:20 UTC (Mon) by jreiser (subscriber, #11027) [Link] (3 responses)

>It is legal to call malloc(0), and it is legal for malloc(0) to return a value other than NULL. If it does so, then the only thing you're allowed to do with that value is pass it to free()

One of the uses of malloc(0) is to serve as an analogue of the Lisp function (gensym): return a unique value, one that does not point to anything else returned by malloc, both now and for the remaining life of the process. (Yes, the standard requires this.) An implementation for which malloc(0) always returns NULL is not acceptable. So in practice, many implementations begin with something like size += (0==size), i.e. malloc(0) is treated as malloc(1).

malloc(0)

Posted Jan 31, 2022 1:31 UTC (Mon) by ABCD (subscriber, #53650) [Link]

An implementation for which malloc(0) always returns NULL is expressly permitted by the POSIX standard:

If the size of the space requested is 0, the behavior is implementation-defined: the value returned shall be either a null pointer or a unique pointer.

The C17 standard says something similar:

If the size of the space requested is zero, the behavior is implementation-defined: either a null pointer is returned to indicate an error, or the behavior is as if the size were some nonzero value, except that the returned pointer shall not be used to access an object.

malloc(0)

Posted Jan 31, 2022 9:43 UTC (Mon) by NYKevin (subscriber, #129325) [Link] (1 responses)

The null pointer satisfies all of the technical requirements of a heap-allocated zero-length array (you can form a pointer that is one past the end, you can iterate over it zero times with a standard for loop, and you can pass it to realloc without breaking anything), and all heap-allocated zero-length arrays are functionally identical because they have no state which can be mutated, so returning NULL can be thought of as semantically equivalent to a copy elision (i.e. you could think of all heap-allocated zero-length arrays as "copies" of the zero-length array that lives at NULL, and then you can elide those copies because zero-length arrays are immutable, so making a copy is unnecessary).

The fact that it happens to vaguely resemble some feature of Lisp, but the standard does not actually require it to fulfill all the requirements of that feature is, frankly, Lisp's problem, not C's problem.

malloc(0)

Posted May 28, 2026 11:13 UTC (Thu) by alx.manpages (subscriber, #145117) [Link]

> The null pointer satisfies all of the technical requirements of a heap-allocated zero-length array

This is not correct. You're allowed to do pointer arithmetic on malloc(0) if and only if it returns a non-null pointer. p+0 is valid for any non-NULL p, but NULL+0 has undefined behavior. This limitation of NULL pointers was lifted recently (ISO C2y), but historically it was UB; also, that change might have unexpected consequences, such as lower quality of static analysis, so I suggest not relying too much on it.

P.S.: Sorry for resurrecting this. I was researching the guarantees on argv[0] and read this incorrect statement by chance.

Handling argc==0 in the kernel

Posted Jan 31, 2022 10:02 UTC (Mon) by larkey (guest, #104463) [Link]

Fair enough, although I *think* C restricts the meaning of array to things that are declared T D[N]; Zero-allocations are definitely possible though, yes, and POSIX is a bit more lax about wording since they say "argv is an array" while, in C terminology, it's "just" a pointer to some memory that... and so on.

Anyway, the POSIX standard pretty clearly says that argv is NULL-terminated, that is, not by itself a zero-length array, but at least

char **argv = { NULL };


Copyright © 2026, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds