Small C "Puzzle"

R

Robert Gamble

I was taking a look at some of the C puzzles at:
http://purana.csa.iisc.ernet.in/~gkumar/cquestions.html and have not had
any trouble with any of them except for the first one which is reproduced
below:

The following C program segfaults of IA-64, but works fine on IA-32.
int main()
{
int* p;
p = (int*)malloc(sizeof(int));
*p = 10;
return 0;
}
Why does it happen so?

Now I know that architecture dependant behavior is off-topic, but I don't
really see anything wrong with the code (yes, stdlib.h is missing, int
main(void), and malloc casted, but I don't think this has anything to do
with the point of the problem). Am I missing something here? I don't
have access to an IA-64 machine, anyone who does have any insight?

Thanks,

Rob Gamble
 
K

Kristofer Pettijohn

Robert Gamble said:
I was taking a look at some of the C puzzles at:
http://purana.csa.iisc.ernet.in/~gkumar/cquestions.html and have not had
any trouble with any of them except for the first one which is reproduced
below:

The following C program segfaults of IA-64, but works fine on IA-32.
int main()
{
int* p;
p = (int*)malloc(sizeof(int));
*p = 10;
return 0;
}
Why does it happen so?

Did you check to make sure p != NULL ? There may have been a malloc() failure
for whatever reason.
 
K

Keith Thompson

Robert Gamble said:
I was taking a look at some of the C puzzles at:
http://purana.csa.iisc.ernet.in/~gkumar/cquestions.html and have not had
any trouble with any of them except for the first one which is reproduced
below:

The following C program segfaults of IA-64, but works fine on IA-32.
int main()
{
int* p;
p = (int*)malloc(sizeof(int));
*p = 10;
return 0;
}
Why does it happen so?

Now I know that architecture dependant behavior is off-topic, but I don't
really see anything wrong with the code (yes, stdlib.h is missing, int
main(void), and malloc casted, but I don't think this has anything to do
with the point of the problem). Am I missing something here? I don't
have access to an IA-64 machine, anyone who does have any insight?

The missing "#include <stdlib.h>" is exactly the problem.

In the absence of a visible prototype for malloc(), the compiler
assumes that it returns int. Since int is 32 bits, but the actual
void* value that malloc() *tries* to return is 64 bits, the result is
undefined behavior. In this case, the undefined behavior manifests
itself as returning the low-order (I think) 32 bits of the pointer
value. Since the high-order bits include non-zero values, this loses
information. Casting to int* masks the error, and generates an
invalid pointer value (whose upper half happens to be all-bits-zero).
Dereferencing the pointer value on the next line causes more undefined
behavior, which manifests itself as a seg fault.

It happens to work on IA-32 (more commonly known as x86) because int
and pointer types are both 32 bits. The phrase "happens to work"
means that the undefined behavior manifests itself as the program
doing what you expected it to do. (This is actually worse than what
happens on the IA-64, since it masks the error.)
 
C

CBFalconer

Robert said:
I was taking a look at some of the C puzzles at:
http://purana.csa.iisc.ernet.in/~gkumar/cquestions.html and have
not had any trouble with any of them except for the first one
which is reproduced below:

The following C program segfaults of IA-64, but works fine on IA-32.
int main()
{
int* p;
p = (int*)malloc(sizeof(int));
*p = 10;
return 0;
}
Why does it happen so?

Now I know that architecture dependant behavior is off-topic,
but I don't really see anything wrong with the code (yes,
stdlib.h is missing, int main(void), and malloc casted, but I
don't think this has anything to do with the point of the
problem). Am I missing something here? I don't have access
to an IA-64 machine, anyone who does have any insight?

Yes it does. Remove the unnecessary cast, which has prevented
compiler warnings, and #include <stdlib.h>, and the problem should
go away.
 
M

Mike Wahler

Robert Gamble said:
I was taking a look at some of the C puzzles at:
http://purana.csa.iisc.ernet.in/~gkumar/cquestions.html and have not had
any trouble with any of them except for the first one which is reproduced
below:

The following C program segfaults of IA-64, but works fine on IA-32.
int main()
{
int* p;
p = (int*)malloc(sizeof(int));
*p = 10;
return 0;
}
Why does it happen so?

Now I know that architecture dependant behavior is off-topic, but I don't
really see anything wrong with the code (yes, stdlib.h is missing, int
main(void), and malloc casted, but I don't think this has anything to do
with the point of the problem).

Actually, it *is* the problem. Although 'malloc()'s return value
is cast above, it's already too late. Lacking a proper prototype,
'malloc()' will be assumed to return type 'int'. So the valid
pointer value has already been corrupted by the conversion to
type 'int'. Casting this 'int' back to type 'int*' won't bring
back the original pointer value.(*)
Am I missing something here? I don't
have access to an IA-64 machine, anyone who does have any insight?

Thanks,

(*) On some platforms/implementations (e.g. Windows), such conversions
between 'int' and a pointer type are indeed well defined, but not by
the language.

I'm not familiar with the 'IA' machines, but apparently the puzzle
is referring to the fact that trying to convert a pointer to an
int and back to a pointer will *not* work correctly on those
platforms.

-Mike
 
M

Mike Wahler

Keith Thompson said:
The missing "#include <stdlib.h>" is exactly the problem.

In the absence of a visible prototype for malloc(), the compiler
assumes that it returns int. Since int is 32 bits, but the actual
void* value that malloc() *tries* to return is 64 bits, the result is
undefined behavior. In this case, the undefined behavior manifests
itself as returning the low-order (I think) 32 bits of the pointer
value. Since the high-order bits include non-zero values, this loses
information. Casting to int* masks the error, and generates an
invalid pointer value (whose upper half happens to be all-bits-zero).
Dereferencing the pointer value on the next line causes more undefined
behavior, which manifests itself as a seg fault.

It happens to work on IA-32 (more commonly known as x86)

Ah, so I guess I *do* know what an 'IA' machine is. :)
(In my other post I said I did not.)

-Mike
 
R

Robert Gamble

The missing "#include <stdlib.h>" is exactly the problem.

In the absence of a visible prototype for malloc(), the compiler
assumes that it returns int. Since int is 32 bits, but the actual
void* value that malloc() *tries* to return is 64 bits, the result is
undefined behavior. In this case, the undefined behavior manifests
itself as returning the low-order (I think) 32 bits of the pointer
value. Since the high-order bits include non-zero values, this loses
information. Casting to int* masks the error, and generates an
invalid pointer value (whose upper half happens to be all-bits-zero).
Dereferencing the pointer value on the next line causes more undefined
behavior, which manifests itself as a seg fault.

It happens to work on IA-32 (more commonly known as x86) because int
and pointer types are both 32 bits. The phrase "happens to work"
means that the undefined behavior manifests itself as the program
doing what you expected it to do. (This is actually worse than what
happens on the IA-64, since it masks the error.)

I really did not consider this an option due to the fact that all the
other examples on the site cast malloc and declare main the same way. I
did just check though and all of the other examples do include proper
header files. I was overlooking the obvious thinking that there was a
deeper issue at work.

Thanks for the detailed explanation. Another reason I dismissed this as
my original answer was that I was thinking that int was 64 bits on IA-64
which did not seem any different from what would be happening on a 32-bit
machine. I'd forgotten this was not the case with most enviornments until
I read your post (I don't program on 64-bit machines yet ;)

I didn't want to beleive this was the answer because it didn't seem as
clear-cut as the other puzzles, kind of obvious and anti-climactic, but
you have talked me into it ;)

Thanks,

Rob Gamble
 
J

Jack Klein

The missing "#include <stdlib.h>" is exactly the problem.

In the absence of a visible prototype for malloc(), the compiler
assumes that it returns int. Since int is 32 bits, but the actual
void* value that malloc() *tries* to return is 64 bits, the result is
undefined behavior. In this case, the undefined behavior manifests
itself as returning the low-order (I think) 32 bits of the pointer
value.

Doesn't make any difference what size int or pointers are. The code
has undefined behavior because it calls ANY function with a return
type other than int without a prototype in scope. Period. Size of
anything irrelevant.
 
R

Raju

The following C program segfaults of IA-64, but works fine on IA-32.
The missing "#include <stdlib.h>" is exactly the problem.

In the absence of a visible prototype for malloc(), the compiler
assumes that it returns int. Since int is 32 bits, but the actual
void* value that malloc() *tries* to return is 64 bits, the result is
undefined behavior. In this case, the undefined behavior manifests
itself as returning the low-order (I think) 32 bits of the pointer
value. Since the high-order bits include non-zero values, this loses
information. Casting to int* masks the error, and generates an
invalid pointer value (whose upper half happens to be all-bits-zero).
Dereferencing the pointer value on the next line causes more undefined
behavior, which manifests itself as a seg fault.

It happens to work on IA-32 (more commonly known as x86) because int
and pointer types are both 32 bits. The phrase "happens to work"
means that the undefined behavior manifests itself as the program
doing what you expected it to do. (This is actually worse than what
happens on the IA-64, since it masks the error.)

I am little confused about the explanation.
I think size of "int" is size of processor register length.
And malloc retuns 64 bit address and pointer is also 64 bit (whether
int * or char *) , then where actually the size giving problem.

And can i know how the code below behaves on both platforms,

int main()
{
char* p;
p = (char*)malloc(sizeof(char));
*p = 10;
return 0;
}
 
M

Malcolm

Raju said:
I am little confused about the explanation.
I think size of "int" is size of processor register length.
And malloc retuns 64 bit address and pointer is also 64 bit (whether
int * or char *) , then where actually the size giving problem.

And can i know how the code below behaves on both platforms,

int main()
{
char* p;
p = (char*)malloc(sizeof(char));
*p = 10;
return 0;
}

int *s and char *s are almost always the same size. ints are typically the
size of data registers, which are commonly but not always the size of
address registers (and on many architectures a register has a dual use). So
in early versions of C it was in fact accepted to assign a pointer to an
int, which is why we have the current problem of prototypes being essential
to make things work correctly.

If an int and a pointer are both 64 bits, then the undefined behaviour of
calling malloc() without a protoype in scope should normally be the
behaviour the programmer expects. In this case it appears that int was 32
bits and pointers 64 bits, so the ujndefined beahviour instead manifested
itself as a crash.
However there could be problems even if ints and pointers are the same
width, for instance the act of loading an address into a data register could
trigger a crash. With undefined behaviour, you just don't know.
 
K

Keith Thompson

Jack Klein said:
in comp.lang.c: [...]
In the absence of a visible prototype for malloc(), the compiler
assumes that it returns int. Since int is 32 bits, but the actual
void* value that malloc() *tries* to return is 64 bits, the result is
undefined behavior. In this case, the undefined behavior manifests
itself as returning the low-order (I think) 32 bits of the pointer
value.

Doesn't make any difference what size int or pointers are. The code
has undefined behavior because it calls ANY function with a return
type other than int without a prototype in scope. Period. Size of
anything irrelevant.

You're right, of course. (I did mention later that it's also
undefined behavior on the IA-32, where int and pointer types are the
same size, but in the quoted paragraph I inadvertently implied a
cause-and-effect relationship that doesn't exist.)

The difference in size does make a difference, in this case, in the
visible result of the undefined behavior.
 
K

Keith Thompson

I am little confused about the explanation.
I think size of "int" is size of processor register length.
And malloc retuns 64 bit address and pointer is also 64 bit (whether
int * or char *) , then where actually the size giving problem.

The size of "int" is whatever the compiler implementer decides it
should be. On the IA-64 (also known as Itanium), int is 32 bits, and
pointers are 64 bits. More precisely, these are the sizes chosen by
the compilers I'm familiar with on IA-64 systems (gcc and Intel's
compiler under Linux). Note that there's a strong motivation for
different compilers on the same system to be compatible with each
other.

Since the IA-64 is a 64-bit system, it's natural to assume that int
should be 64 bits, but that would actually cause some problems.
Currently, the types of the predefined integer types are:

char 8 bits
short 16 bits
int 32 bits
long 64 bits
long long 64 bits

If int were 64 bits, short would be either 16 or 32 bits, leaving a
gap in the type system. (C99 allows extended integer types, with
but portable code can't yet depend said:
And can i know how the code below behaves on both platforms,

int main()
{
char* p;
p = (char*)malloc(sizeof(char));
*p = 10;
return 0;
}

With the understanding that it's undefined behavior regardless
of the underlying system:

On IA-32: no ouput.
On IA-64: Segmentation fault.

Here's another program that shows more clearly what's going on:

#ifdef INCLUDE_STDLIB
#include <stdlib.h>
#endif
#include <stdio.h>
int main()
{
char* p;
p = (char*)malloc(sizeof(char));
printf("p = %p\n", p);
fflush(stdout);
*p = 10;
return 0;
}

On IA-32, this produces nearly the same output whether INCLUDE_STDLIB
is defined or not:

p = 0x8648008

(The specific address changes for some irrelevant reason.)

On IA-64, if INCLUDE_STDLIB is not defined, the output is:

p = 0xc80
Segmentation fault

If INCLUDE_STDLIB is defined, the output is:

p = 0x6000000000000c80

Of course none if this arises if you just write the code properly in
the first place: use a "#include <stdlib.h>" directive, and don't cast
the result of malloc().
 
G

Gordon Burditt

If an int and a pointer are both 64 bits, then the undefined behaviour of
calling malloc() without a protoype in scope should normally be the
behaviour the programmer expects. In this case it appears that int was 32
bits and pointers 64 bits, so the ujndefined beahviour instead manifested
itself as a crash.

The unstated assumption here, which is false, is that the linkage
conventions for two types of the same size are the same. This is
often not true. Even on an Intel [3456]86 platform, integers and
floating point numbers are often (but not always) returned in a
different place.

For example, on a 680X0 processor it is quite possible (and logical)
to return pointers in the a0 register and integers in the d0 register
and floating point numbers in the top of the stack of the floating-point
unit. If the return type of the function is not known, the calling
code may retrieve the return value *FROM THE WRONG PLACE*. No amount
of casting the result will fix that.
However there could be problems even if ints and pointers are the same
width, for instance
the act of loading an address into a data register could
trigger a crash.
This I would think would be rather unusual. However, loading an
address register with uninitialized or otherwise arbitrary bit
patterns is much more likely to cause a crash.
With undefined behaviour, you just don't know.

If you load data from the WRONG register, copying it to the "right"
register won't help, and the issue that loading crap into a register
might cause problems is a completely separate problem.

Gordon L. Burditt
 
N

Nudge

Mike said:
I'm not familiar with the 'IA' machines, but apparently the
puzzle is referring to the fact that trying to convert a pointer
to an int and back to a pointer will *not* work correctly on
those platforms.

AFAIK, IA stands for Intel Architecture.

Thus IA-32 is Intel's 32-bit ISA, i.e. x86, and IA-64 is Intel's
64-bit ISA, i.e. the Itanium Processor Family ISA.
 
M

Minti

Raju said:
I am little confused about the explanation.
I think size of "int" is size of processor register length.
And malloc retuns 64 bit address and pointer is also 64 bit (whether
int * or char *) , then where actually the size giving problem.


And can i know how the code below behaves on both platforms,

int main()
{
char* p;
p = (char*)malloc(sizeof(char));
*p = 10;
return 0;
}

Yup you are right. But you might note that even if a compiler is supporting
64 bit arch. It will in many cases keep int's to be 32 bit's { to keep
backward compatiblity for applications written assuming int to be 32 bits }
That said I think that is just a guess. For I really haven't used any 64 bit
compilers. Can anyone share the current trends in this 64 bit industry. gcc,
MS cl etc.
 
M

Minti

Jack Klein said:
Doesn't make any difference what size int or pointers are. The code
has undefined behavior because it calls ANY function with a return
type other than int without a prototype in scope. Period. Size of
anything irrelevant.


Yes, but the question is why it would work on IA-32 not IA-64. And IA-32 has
same size and representation specification for pointers and int's alike.
 
M

Minti

Keith Thompson said:
The size of "int" is whatever the compiler implementer decides it
should be. On the IA-64 (also known as Itanium), int is 32 bits, and
pointers are 64 bits. More precisely, these are the sizes chosen by
the compilers I'm familiar with on IA-64 systems (gcc and Intel's
compiler under Linux). Note that there's a strong motivation for
different compilers on the same system to be compatible with each
other.

Since the IA-64 is a 64-bit system, it's natural to assume that int
should be 64 bits, but that would actually cause some problems.
Currently, the types of the predefined integer types are:

char 8 bits
short 16 bits
int 32 bits
long 64 bits
long long 64 bits

If int were 64 bits, short would be either 16 or 32 bits, leaving a
gap in the type system. (C99 allows extended integer types, with


With the understanding that it's undefined behavior regardless
of the underlying system:

On IA-32: no ouput.
On IA-64: Segmentation fault.

Here's another program that shows more clearly what's going on:

#ifdef INCLUDE_STDLIB
#include <stdlib.h>
#endif
#include <stdio.h>
int main()
{
char* p;
p = (char*)malloc(sizeof(char));
printf("p = %p\n", p);
fflush(stdout);
*p = 10;
return 0;
}

On IA-32, this produces nearly the same output whether INCLUDE_STDLIB
is defined or not:

p = 0x8648008

(The specific address changes for some irrelevant reason.)

On IA-64, if INCLUDE_STDLIB is not defined, the output is:

p = 0xc80
Segmentation fault

If INCLUDE_STDLIB is defined, the output is:

p = 0x6000000000000c80

Of course none if this arises if you just write the code properly in
the first place: use a "#include <stdlib.h>" directive, and don't cast
the result of malloc().


Why not cast? Are you saying this in context with the IA-64 thingy or just
as a general thingy? I probably have never understood the need to cast or
not to cast. I seem to consider it to be more of a matter of taste rather
than anything else now. I have seen lot of debate, Bjarne says use cast,
everyone here says no.
 
K

Keith Thompson

Minti:

You e-mailed me a question in response to something I posted on this
thread. Please post any questions to the newsgroup. If you must send
me e-mail, don't use a spam-blocked return address. (If kissmyass.com
is meant to be a spam trap, you should pick a different one; it
happens to be a real domain, and you could be subjecting its owner to
spam that was meant for you.)

In answer to your question, it is never necessary to cast the result
of malloc() in C; it only masks errors. This is from the latest
version of the C FAQ (the web version hasn't yet been updated):

7.7: Why does some code carefully cast the values returned by malloc
to the pointer type being allocated?

A: Before ANSI/ISO Standard C introduced the void * generic pointer
type, these casts were typically required to silence warnings
(and perhaps induce conversions) when assigning between
incompatible pointer types.

Under ANSI/ISO Standard C, these casts are no longer necessary,
and in fact modern practice discourages them, since they can
camouflage important warnings which would otherwise be generated
if malloc() happened not to be declared correctly; see question
7.6 above. (However, the casts are typically seen in C code
which for one reason or another is intended to be compatible
with C++, where explicit casts from void * are required.)

References: H&S Sec. 16.1 pp. 386-7.
 
M

Minti

Keith Thompson said:
Minti:

You e-mailed me a question in response to something I posted on this
thread. Please post any questions to the newsgroup. If you must send
me e-mail, don't use a spam-blocked return address. (If kissmyass.com
is meant to be a spam trap, you should pick a different one; it
happens to be a real domain, and you could be subjecting its owner to
spam that was meant for you.)



Thanks, my mail program is configured to send both mail and post it to the
newsgroup, however as a I later checked up after disconnecting that it had
failed to upload the message. Ooops.


Thanks. Again.
 
C

CBFalconer

Minti said:
.... snip ...

Why not cast? Are you saying this in context with the IA-64 thingy
or just as a general thingy? I probably have never understood the
need to cast or not to cast. I seem to consider it to be more of a
matter of taste rather than anything else now. I have seen lot of
debate, Bjarne says use cast, everyone here says no.

He is talking about C++. Here we talk about C. They are not the
same language.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,731
Messages
2,569,432
Members
44,832
Latest member
GlennSmall

Latest Threads

Top