Is this undefined behavior in C?

  • Thread starter Thread starter jim mcnamara
  • Start date Start date
  • Tags Tags
    Behavior
Join the discussion
Ask a follow-up here, or get your own question answered by working scientists, mathematicians and engineers — people, not an autocomplete.
Real named experts · corrections over time · the nuance an AI answer skips
11 replies · 3K views
Messages
4,789
Reaction score
3,854
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:
Physics 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:
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.
 
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.
 
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.
 
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.
 
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.