1

I'm new to dynamic and static allocation and I noticed a strange behaviour. I made the following code snippet :

#include ...
int main(void){

    int i ;
    printf(" using amesand :%p\n", &i);
    
    int *j = malloc(sizeof(*j)) ;
    printf(" using malloc :%p\n", j)

}

which then compiles and produces :

 using amesand :0x7ffc0128695c
 using malloc :0x6686b0

I have read somewhere that

all pointers have the same width within the same implementation.

Can someone explain why is the pointer returned by malloc() only 3 bytes long ?

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
Ait-Gacem Nabil
  • 165
  • 3
  • 12

3 Answers3

2

to determine the width of pointers use the sizeof operator, For example:

int i;
printf(" using amesand :%zu\n", sizeof( &i ));

int *j = malloc(sizeof(*j));
printf(" using malloc :%zu\n", sizeof( j) );

As for this output then addresses can have different values depending where the corresponding memory was allocated. And using the conversion specifier %p generates usually (as in your case) an output where leading zeroes are skipped.

Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
1

all pointers have the same width within the same implementation.

Yes for strictly conforming C programs at least. There's common non-standard extensions where larger pointers are mixed with ordinary ones, usually with the non-standard keyword far.

However that isn't the problem here, but merely your use of printf. By default it removes leading zeroes. Not only for hex but for any number. printf("%d", 1) prints 1 right? And not 0000000001 just because int happens to be 4 bytes = 10 decimal digits. Pointers and hex are no different, leading zeroes are removed by default until you tell the function otherwise.

If you wish to print a hex number corresponding to the size of the pointer, you'd use %.16p for 16 digits = 8 byte large pointers. Or variably/portably, like this:

printf(" using malloc :%.*p\n", sizeof(j)*2, j);

Corrected example tested on x86_64 Linux:

#include <stdio.h>
#include <stdlib.h>

int main(void){

    int i ;
    printf(" using amesand:\t%.*p\n", sizeof(&i)*2, &i);
    
    int *j = malloc(sizeof(*j)) ;
    printf(" using malloc:\t%.*p\n", sizeof(j)*2, j);
}

Output:

using amesand:  0x00007ffe8710871c
using malloc:   0x0000000000ed82b0

Regarding why the different memory types have different addresses, see this:
A program uses different regions of memory for static objects, automatic objects, and dynamically allocated objects

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 1
    Re “Yes for strictly conforming C programs at least”: The C standard does not require all pointers to have the same size, even for strictly conforming programs. – Eric Postpischil Oct 06 '21 at 10:54
  • 1
    @EricPostpischil It does, implicitly, by 6.3.2.3/7 pointer to pointer coversions. "Otherwise, when converted back again, the result shall compare equal to the original pointer." The only practical way to conform with that is to make all pointers the same size. – Lundin Oct 06 '21 at 10:57
  • 1
    No, it is not. In an implementation with hardware word-addressing, addresses that must be aligned, such as `int` can use fewer bits (with just the hardware address) than addresses that need added information (such as `void *` or `char *`, which would contain additional bits for byte-within-a-word). Converting an `int *` to `char *` would yield a larger pointer with more bits. Converting back would remove the bits. Removing the bits does not lose required information because the conversion and equality test only need to work if the pointer is aligned as required for the `int`. – Eric Postpischil Oct 06 '21 at 11:00
  • 1
    And whether it is practical or not is irrelevant; the C standard does not require it regardless of whether it is practical or not. – Eric Postpischil Oct 06 '21 at 11:01
  • 1
    @EricPostpischil That doesn't make any sense: for that same fictional system, you must also be able to convert from the "larger" `int*` to the "smaller" `char*` and back, without losing part of the address. 6.3.2.3/7 is universal for all object pointers. The only way to implement that without having the same size is some sort of table or temporary buffer elsewhere keeping track of original pointer values, which would be slow and cumbersome in practice. – Lundin Oct 06 '21 at 11:07
  • 1
    You have the sizes wrong; the `int *` is smaller (it does not have byte-within-word bits), and the `char *` is larger (it has byte-within-word bits). As my comment states, “Converting an `int *` to `char *` would yield a **larger** pointer with more bits.” – Eric Postpischil Oct 06 '21 at 11:13
  • 1
    @EricPostpischil Whatever, call them `apple*` and `orange*` if you wish. You must still be able to covert from one pointer type and back, and that cannot be done in any sensible way if one pointer type is smaller. Also this is a pretty ridiculous discussion since systems with varied pointer sizes are very much a real thing, hundreds of implementations exist and _all_ of them use `fear`/`near` non-standard pointer qualifiers to solve this. Most famously MS DOS, but pretty much every semi-modern 8 or 16 bit MCU as well. – Lundin Oct 06 '21 at 11:21
  • 1
    Yes, it can be done in a sensible way. The conversion from `char *` to `int *` is only required to work and to produce a result that compares equal to the original if the `char *` is suitably aligned for an `int *`. That means the byte-within-word bits of the `char *` must be zero, and no information is lost in the conversion. The `int *` does not need the byte-within-word bits because they are always zero for the address of an `int`. If the byte-within-word bits of a `char *` are not zero, the behavior of converting it to an `int *` is not defined by the C standard. – Eric Postpischil Oct 06 '21 at 11:24
  • 1
    @EricPostpischil There is no requirement by the standard that a system must be limited to aligned access. Which isn't the case on the mentioned 8-bitters and not on many 16-bitters either. "Correctly aligned" in such systems means 1 byte (no) alignment. – Lundin Oct 06 '21 at 11:28
  • 1
    Whether the C standard requires a system be limited to aligned access is irrelevant. The C standard allows a system to require alignment. In a system that requires alignment, pointers to objects with greater alignment requirements do not require (for information purposes) as many bits as pointers to objects with lesser alignment requirements. – Eric Postpischil Oct 06 '21 at 11:40
  • 1
    [FYI.](https://stackoverflow.com/questions/1241205/are-all-data-pointers-the-same-size-in-one-platform-for-all-data-types) – Eric Postpischil Oct 06 '21 at 12:32
  • 1
    @EricPostpischil And look, it is saying the very same thing as I've already told you above. https://stackoverflow.com/a/1241979/584518. – Lundin Oct 06 '21 at 13:17
  • No, it does not say the very same thing you said. The answer you link to says that on Freescale’s HCS12 processor, function pointers are 16 bits for near pointers and 24 bits for far pointers. That is, it says there exists at least one system that uses the near/far model you referred to. However, it does not say what you said, whihc is that “all of them” do this. However, I pasted the wrong link; I intended to link to [this answer](https://stackoverflow.com/a/1539196/298225). – Eric Postpischil Oct 06 '21 at 21:01
  • @EricPostpischil It's not rocket science: if you have a system with 16 bit address bus, but need more than a 64kb memory map (registers+RAM+flash), you need some oddball feature like extended pointers to solve that. This is pretty much how every single low-end microcontroller out there works. – Lundin Oct 07 '21 at 06:40
1

With %p, your C implementation simply omits leading zeros from pointer values.

Printing “0x6686b0” does not indicate the pointer is three bytes long; it merely indicates the value is 6686b016, which equals 006686b016, 00000000006686b016, or 0000000000000000000000000000000006686b016. Leading zeros have been omitted.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312