4
votes

There have been several discussions on whether accessing uninitialized variables yields undefined behaviour (e.g. in this SO answer), and I've looked through this online C11 draft standard concerning indeterminate values and undefined behaviour, too.

What I found on SO and in the standard (maybe I've overlooked something), undefined behaviour when accessing uninitialized variables is related to trap representations or to the possibility of having an (implicit) register storage class.

But what if the respective variable is an array (which cannot get register storage class), and the datatype must not have a trap representation (like character type according to 6.2.6.1p5 )?

Is then accessing such a value still UB?

int main () {
    char output[10];
    for (int i=0; i<10; i+= 2) {  // initializing every 2nd element only
        output[i] = '0' + i;
    }
    char c = output[1]; // accesses something "uninitialized"; But is it UB?
    printf("%c\n", c);  // prints probably garbage; But what if I don't care?
    return 0;
}
2
@PeterJ OK, then make it an answer where you explain why, and back it up with quotes from the standard. I will gladly upvote it. - PSkocik
@PeterJ Then why not link one of those 10000000000 times where it was answered? - interjay
@PeterJ Uninitialized auto pointer usually can have been declared register. The array in this piece of code can't, and that makes the difference, as far as I understand - PSkocik
@PeterJ: This particular question has not been answered many times. In fact, even simpler versions of this question using scalars instead of arrays have been answered but the answers are contradictory (some say UB, others say not UB, both look plausibly correct to regular humans). - John Zwinck
@David C. Rankin: But 6.3.2.1p2 still requires "...that could have been declared with the register storage class" to invoke UB, right? - Stephan Lechner

2 Answers

3
votes

This type question and discussion is always a challenge, because it requires the interpretation of the C-standard, which in many aspects, isn't written for clarity, but is more the result of deliberation and compromise between what two (or more) competing factions will agree to include in it. After having gone through it a number of times, it is clear that far more discussion went into what to, or not to, include than ever went into where or how to include it in the standard for readability.

Continuing from the comments, I think we all can agree, based on the number of times it has been referenced in the comments and answers, that C11 Standard (draft n1570) § 6.3.2.1 Lvalues, arrays, and function designators (¶2)) applies.

"If the lvalue designates an object of automatic storage duration that could have been declared with the register storage class (never had its address taken), and that object is uninitialized (not declared with an initializer and no assignment to it has been performed prior to use), the behavior is undefined."

(emphasis mine)

The issue becomes, "Is an array with automatic storage something that could have been declared with the register storage class?"

At first look, the obvious thought is "An array with a register storage-class specifier? That would be pretty dumb, you can't take the address, how would you ever access the values?" Given § 6.2.5 Types (comment 36)) "The address of such an object is taken implicitly when an array member is accessed."

First thoughts are often wrong, because arrays with automatic storage allow the use of the register storage class. § 6.7.1 (6 & comment 121)

The following code is perfectly legal -- while arguably not that useful.

#include <stdio.h>

int main (void) {

    register int a[] = { 1, 2, 3, 4 };
    register size_t n = sizeof a / sizeof (int);

    printf ("n : %zu\n", n);

    return 0;
}

The only operators that can be applied to an array declared with storage-class specifier register are sizeof and _Alignof. (See § 6.7.1 (comment 121)

Given the above, and given any uninitialized element in the array "is uninitialized (not declared with an initializer and no assignment to it has been performed prior to use), the behavior is undefined."

In your specific case:

    char c = output[1]; // accesses something "uninitialized"; But is it UB?

output[1] designates an object of automatic storage duration that could have been declared with the register storage class (never had its address taken), and that object is uninitialized (not declared with an initializer and no assignment to it has been performed prior to use), the behavior is undefined.

2
votes

From the C Standard (6.2.6 Representations of types, 6.2.6.1 General)

5 Certain object representations need not represent a value of the object type. If the stored value of an object has such a representation and is read by an lvalue expression that does not have character type, the behavior is undefined....

So for character arrays there is no undefined behavior.

Objects of character types do not have a trap representation.