Is this undefined behavior in C?

  • Thread starter Thread starter jim mcnamara
  • Start date Start date
  • Tags Tags
    Behavior
Click For Summary

Discussion Overview

The discussion revolves around the concept of undefined behavior (UB) in the C programming language, specifically analyzing several code snippets (A, B, C, D, E) to determine whether they exhibit UB according to the C11 standard. Participants explore various interpretations of the standard and the implications of UB in different scenarios.

Discussion Character

  • Debate/contested
  • Technical explanation
  • Conceptual clarification

Main Points Raised

  • Some participants argue that example E does not exhibit UB because it compiles without errors, while others maintain that division by zero is inherently UB.
  • There is contention regarding example D, with some asserting that it does not lead to UB as it simply returns a predictable value, while others suggest it is implementation-defined behavior.
  • Example C is widely agreed upon as exhibiting UB due to the use of an uninitialized variable.
  • In example B, some participants claim that it does not compile due to type incompatibility between void* and int*, while others argue that it is acceptable in C, noting that the error handling for malloc is appropriately managed.
  • Example A is debated, with some stating that unsigned arithmetic is well-defined, while others suggest it leads to implementation-defined behavior.

Areas of Agreement / Disagreement

Participants do not reach a consensus on whether all examples exhibit UB. Multiple competing views remain, particularly regarding examples E, D, and B, with differing interpretations of the C standard and compiler behavior.

Contextual Notes

Participants highlight limitations in their interpretations, such as assumptions about compiler behavior, the definition of UB, and the compatibility of pointer types in C. There are also references to specific compiler outputs and behaviors that may vary across different environments.

Who May Find This Useful

Readers interested in C programming, undefined behavior, compiler design, and the nuances of the C standard may find this discussion relevant.

jim mcnamara
Mentor
Messages
4,789
Reaction score
3,852
I really would appreciate seeing what opinions* there are on undefined behavior in C. Based
on only the code snippets (A, B, C, D, E (yes, they have issues)) below and:

From N1570 (C 11 standard draft)
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf

This is what the standard says about Undefined Behavior, definitions:
Section 3.4:
behavior
external appearance or action

Section 3.4.3:

undefined behavior
behavior, upon use of a nonportable or erroneous program
construct or of erroneous data, for which this International Standard imposes no
requirements

NOTE: Possible undefined behavior ranges from ignoring the situation completely
with unpredictable results, to behaving during translation or program execution
in a documented manner characteristic of the environment (with or without the
issuance of a diagnostic message), to terminating a translation or execution
(with the issuance of a diagnostic message).
Code:
// Specifically, do these snippets result in UB:

// A
unsigned diff(unsigned a, unsigned b)
{
   return a - b;
}

// B
int *foo(void)
{
   int *p=malloc(4);   
   if(p)
     *p=13;
   return p;
}

// C
int foo(void)
{
   foo_2();
   int x;
   return x/2;
}

// D
int main(void)
{
   int i=0;
   return ( i + 1 ) + ( i + 2);
}

//E
int main(void)
{
   int i=13;
   5/0;
   return i;
}
Can you defend your position? There are discussions like this elsewhere on the internet.
* I used the word opinions above on purpose. I will explain later.
 
Last edited:
Technology news on Phys.org
First of all, all any example that does not compile does not exhibit undefined behavior, it is simply illegal. So your example E would not compile, as the compiler would issue a "dividing by 0" warning.

I don't see any problem with example D - why would that give undefined behavior? It just returns 3.
Example C does give undefined behavior, apart from any effects that foo_2() might have the variable x is not initialized.
B again doesn't compile, because void* and int* are incompatible. If it were, I think it would be fine, except that you are leaking memory (but that's not undefined behavior).

A is the one I'm not sure about, I guess if you would call diff(3, 9) you would get a very large number, which is completely predictable but not what you meant.
 
A. No. The difference between two unsigned ints is always well-defined, even in the case that a < b. Unsigned arithmetic is modulo N+1, where N is the largest possible value represented by that unsigned int. What you do get is implementation-defined behavior because the standard doesn't specify a value for N.

B. Yes. You are assuming that sizeof(int)=4. There's no guarantee that this is the case.

C. Yes. Using a variable (in this case, x) before it has been assigned a value is UB.

D. No. Returning something other than 0, EXIT_SUCCESS, or EXIT_FAILURE is implementation-defined behavior, not undefined behavior.

E. Yes. Just because you are dropping 5/0 on the bit floor doesn't excuse you from the fact that 5/0 is UB.
 
CompuChip said:
First of all, all any example that does not compile does not exhibit undefined behavior, it is simply illegal. So your example E would not compile, as the compiler would issue a "dividing by 0" warning.
E compiles quite fine for me, multiple compilers. That pesky warning is just that, a warning. The compiler is being nice in detecting that UB at compile time. The nastiest thing about undefined behavior is that while it is illegal (by definition), the compiler can do *anything* in response to it and still be compliant with the standard.

B again doesn't compile, because void* and int* are incompatible. If it were, I think it would be fine, except that you are leaking memory (but that's not undefined behavior).
C != C++. void* is compatible with every pointer type in C. The recommended practice in many places is *not* to cast the return from malloc to the target type.
 
Last edited:
@compuchip define "compile"
So, No:
jmcnama@SNED ~> cat t.c
int main(void)
{
int i=13;
5/0;
return i;
}
jmcnama@SNED ~> cc t.c -o t
t.c: In function `main':
t.c:4: warning: division by zero
jmcnama@SNED ~> ./t
jmcnama@SNED ~>
jmcnama@SNED ~> echo $?
13
jmcnama@SNED ~> uname -a
SunOS SNED 5.10 Generic_144488-14 sun4u sparc SUNW,SPARC-Enterprise
jmcnama@SNED ~> gcc --version
gcc (GCC) 3.4.3 (csl-sol210-3_4-branch+sol_rpath)

It does run and does not segfault. Here is why: the compiler detected statically defined UB and then did what it felt like doing -- 5/0; has no lvalue so it was ignored assember output, notice please NO division:
jmcnama@SNED ~> cc -S t.c
t.c: In function `main':
t.c:4: warning: division by zero
jmcnama@SNED ~> cat t.s
.file "t.c"
.section ".text"
.align 4
.global main
.type main, #function
.proc 04
main:
!#PROLOGUE# 0
save %sp, -120, %sp
!#PROLOGUE# 1
mov 13, %g1
st %g1, [%fp-20]
ld [%fp-20], %g1
mov %g1, %i0
ret
restore
.size main, .-main
.ident "GCC: (GNU) 3.4.3 (csl-sol210-3_4-branch+sol_rpath)"
 
Last edited:
jim mcnamara said:
It does run and does not segfault.
On your computer and with your compiler, that is. Undefined behavior gives the compiler and runtime environment free reign to do anything at all and still be compliant with the standard.
 
You are correct - the compiler is not required by the standard to do anything - it is free to do as it pleases. Or nothing. It could also reformat all of the filesystems on the computer. It chose to emit a NOP.
 
Any comments on my post #3?
 
For B, in addition to the sizeof(int) explicitly being set to 4, p can be garbage when returned. If the malloc returned NULL, the error needs to be processed. Perhaps you are out of memory.
 
  • #10
The error *is* being handled in B. *p is set only if p isn't a null pointer. What kind of error handling do you want? A printed error message? A longjmp? That kind of error handling is best left to the calling routine in C. That malloc failed to allocate memory is indicated by the function returning a null pointer. That's typically just what the doctor ordered in C.
 
  • #11
Whoa. Mixed up there for B.

B: int *p=malloc(4);
would allocate space for 4 char, an array of 4 char so to speak, at least for K&R C. ( I am not sure how that plays out in later C ). Is a cast to int* necessary. This should work for early compiler editions of C.

I was thinking that the int would or could overrun the space and thus garbage would result to a retrieved value of where where p is pointing later in the program.

Anyways, you have allocated space of 4 char and assigning an int of unknown length to that space. What's that called - loose typing.

Sorry.
 
  • #12
256bits said:
Is a cast to int* necessary.
No.

malloc() returns a void* pointer that is either null or points to the start of a contiguous chunk of memory that is suitably aligned for use as any of the fundamental types (so use as an int* pointer is no problem) and whose size is measured in chars. In C (but not in C++), void* is compatible with any type. Any pointer can be converted to void* without a cast, and a void* can be converted to a pointer of some other type without a cast.
 

Similar threads

  • · Replies 4 ·
Replies
4
Views
3K
  • · Replies 11 ·
Replies
11
Views
4K
  • · Replies 3 ·
Replies
3
Views
2K
  • · Replies 5 ·
Replies
5
Views
2K
  • · Replies 4 ·
Replies
4
Views
4K
  • · Replies 49 ·
2
Replies
49
Views
12K
  • · Replies 7 ·
Replies
7
Views
13K
  • · Replies 3 ·
Replies
3
Views
6K
  • · Replies 13 ·
Replies
13
Views
7K
  • · Replies 15 ·
Replies
15
Views
5K