Skip to content

UB20⚓︎

Author: Yikai Cui.

Definition⚓︎

An lvalue designating an object of automatic storage duration that could have been declared with the register storage class is used in a context that requires the value of the designated object, but the object is uninitialized. (6.3.2.1)

当一个左值指定了一个“可以用register类约束的”具有自动存储周期的对象,这个对象所在的环境需要他的值,而这个对象并未初始化。

Description⚓︎

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. (from ISO/IEC 9899:2023(E) 6.3.2.1, 2)

如果一个左值指定了一个自动存储周期的对象,这个对象可以用register类来修饰(即从来没有取他的地址),并且这个对象未被初始化(声明时没有经过初始化,在使用之前也没有得到赋值),那么行为是未定义的。

A declaration of an identifier for an object with storage-class specifier register suggests that access to the object be as fast as possible. The extent to which such suggestions are effective is implementation-defined. (from ISO/IEC 9899:2023(E) 6.7.1, 8) However, whether or not addressable storage is used, the address of any part of an object declared with storage-class specifier register cannot be computed, either explicitly (by use of the unary & operator as discussed in 6.5.3.2) or implicitly (by converting an array name to a pointer as discussed in 6.3.2.1).

一个具有register修饰符的标识符定义,是指建议对此对象的访问应该越快越好。但这种建议在何种程度上有效是具体实现时定义的。然而,不管是否使用了可编址存储空间,一个register对象任何部分的地址都是不能被以直接或间接方式计算的。

An object whose address is never taken may (or may not) be allocated in registers, depending on compilers and specific hardware resource of the target machine. Without initialization, the object holds the same binary form as what the register contains before the object has been assigned to it. The last user of the register could have been anything, in fact, the range of register users is larger than the range of memory users, (as many instruction sets store the result of an instrction into a register, MIPS for example). Hence the behavior is undefined.

一个未使用其地址的对象可能(也或许不会)被分配到寄存器中,取决于硬件资源情况和编译器的优化算法。未经初始化时,对象所对应的寄存器保持着在被分配给此对象之前它的模样。寄存器的上一任使用者可以是任何东西,实际上,寄存器的使用者种类比内存使用者的种类大多了(有许多指令集选用寄存器作为存储指令结果的容器,例如MIPS)因此行为是未定义的。

Code⚓︎

UB20.c
#include<stdio.h>
#include<stdlib.h>
#include<time.h>

int f() {
    int tot = rand();
    return tot;
}

int g() {
    int variable;
    printf("%d\n", variable); // Undefined behavior! (1)
}

int main() {
    srand(time(0));
    f(); g(); f(); g();
}
  1. Undefined behavior! Here variable never had its address taken, and could have been declared with register storage class; and is not initialized before printf requires its value.

View source

Configurations⚓︎

OS: Microsoft Windows 11 22H2

gcc -v : gcc version 11.2.0 (GCC), x86_64-w64-mingw32

compile and run commands: gcc UB20.c -o UB20.exe && ./UB20.exe

OS: Microsoft Windows 11 22H2

gcc -v : gcc version 11.2.0 (GCC), x86_64-w64-mingw32

compile and run commands: gcc -O2 UB20.c -o UB20.exe && ./UB20.exe

Behaviors⚓︎

  • Compilation successful
  • Output: 29622 and 16691, vary in every execution.
  • Compilation successful
  • Output: 0 and 0

Advice⚓︎

Programmers are adviced not to introduce uninitialized read in their code, whether or not they are accessing an object with register storage class.

Mainstream IDEs provide static code analysis tools that are able to discover uninitialized reads. If IDE warns, double check to confirm.