Do function declarations in C actually generate object code?

In the discussion about returning the malloc return value, many argued that an implicit malloc declaration would return a convert to int , and then return to T* again, which could lead to a truncation of the pointer in situations where:

 sizeof(int) < sizeof(void*) 

This would mean that the compiler would do the following:

  • Binds and calls the correct object code defining malloc
  • Generates object code to convert the return value to a shorter int type
  • Generates object code to convert back to a larger pointer.

Can someone really prove that this is happening? Tell me with sample code on 64-bit Linux?

I would do it myself, but I do not have access to a 64-bit machine.

+1
c compiler-construction
source share
4 answers

The problem with the description of what is happening is in step 2. If the declaration is implicit, the code on the calling site does not "convert" the return value of the function, really.

What happens is that the calling site code retrieves the return value (usually from the register or from the stack), assuming it is of type int. The procedure for this is different for different OS and compilers and is usually defined by an ABI document.

For the most common ABIs, the return location and the sizes of int and void * are the same, so in fact you will not have any problems with this, although this is not true. This is true for Linux, Windows, and Mac OS X on both 32-bit and 64-bit platforms, I believe that it is 32-bit platforms.

On 64-bit platforms, more often for "long" and "void *" should be the same size, so if you have an implicit declaration for malloc (), the return value will be truncated. Nevertheless, several popular 64-bit programming models.

Back in the "good old days" of DOS development, it was possible to create programs that started in a mode where "int" was 16 bits and the pointers were 32 bits (in fact, 24). In these cases, calling malloc () with an implicit prototype would truncate the return value.

Note that even in cases where the return value is truncated, you still may not have a run-time problem, depending on whether the value is really outside the valid int range.


On Mac OS X in 64-bit mode, this code:

 #include <stdio.h> int main (int argc, const char * argv[]) { int x = malloc(128); void *p = malloc(128); printf("Hello, World!\nsizeof(int)=%d,sizeof(void*)=%d,x=0x%xd,p=%p\n", sizeof(int), sizeof(void *), x, p); return 0; } 

prints:

Hello World! SizeOf (INT) = 4, SizeOf (* invalid) = 8, x = 0x1001c0d, p = 0x100100240

Note that the “x” value has fewer digits than the “p” value, silently resetting the most significant 32 bits of the value. The actual build code for two malloc calls is as follows:

 LM2: movl $128, %edi call _malloc movl %eax, -12(%rbp) LM3: movl $128, %edi call _malloc movq %rax, -8(%rbp) 

So, the correct value is returned by malloc (in% rax), but the movl command truncates it when it moves to the variable "x".

+6
source share

Malloc is declared in the header of the stdlib.h file, and the declaration is included directly using the C preprocessor of your source, which is then linked to the malloc code at later stages.

When you have the code:

 #include <stdlib.h> ... void * foo = malloc(42); 

he actually went into something like

 ... extern void *malloc (size_t __size) __attribute__ ((__nothrow__)) __attribute__ ((__malloc__)) ; (...lots of other declarations...) ... void * foo = malloc(42); 

When you do not include a function prototype, the default is something like

 int malloc(); ... void * foo = malloc(42); 

This means that the final compiled code will do something like "call malloc with argument 42, convert its return value from int to void * and put it in foo ." Then it will be related to libc, which has the pre-compiled malloc object code, which is obviously not valid * extension. Consequently, the result will be one additional int-to-void * conversion in the CPU register that contains the return value. I assume that in an architecture with 64-bit architecture, this could mean accepting lower 32 bits and putting 32 zeros before clearing part of the original pointer.

+1
source share

I think that 2 is not quite a “convincing” transformation, as you mean. When daling with a function whose type is unknown, the compiler should make some assumptions about how many bytes to “capture”. The default size is int.

So, if void * and int are the same size, good and good, if not oops!

+1
source share

Omitting the declaration (prototype) for malloc , the compiler assumes that it returns an int . Therefore, calls to it are generated as code to call a function that returns an int result.

How to do this depends on your system, so the result can be transferred back to the data register, address register, or to the stack.

The compiler then generates additional code to convert the (intended) return value of int to a pointer.

Obviously, this is not what you want. You might be lucky on most systems where ints and pointers are the same width, so converting the return value does virtually nothing, but you cannot rely on this behavior.

So, overall, it’s bad not to declare external functions .

+1
source share

All Articles