Is this undefined behavior in C?

  • Thread starter Thread starter jim mcnamara
  • Start date Start date
  • Tags Tags
    Behavior
AI Thread Summary
The discussion centers on the concept of undefined behavior (UB) in C programming, specifically analyzing several code snippets for potential UB. Example C is identified as having UB due to the use of an uninitialized variable, while example E is debated; some argue it should not compile due to division by zero, while others state it can compile but results in UB. Example D is deemed safe as it returns a predictable value, and example A is clarified to have implementation-defined behavior rather than UB. The conversation highlights the nuances of memory allocation and pointer compatibility in C, emphasizing that UB allows compilers significant leeway in handling erroneous code.
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
11
Views
4K
Replies
3
Views
2K
Replies
5
Views
2K
Replies
4
Views
4K
Replies
49
Views
11K
Replies
7
Views
12K
Replies
13
Views
7K
Back
Top