What is the reason for seemingly inconsistent sizes of pointers and struct types?
Asked Answered
J

4

6

On my machine, the relevant sizes are:
sizeof(char) == 1, sizeof(int) == 4 and sizeof(char*) == 8.

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

typedef struct person{
    char *name;
    int age;
}Person;

int main()
{
    printf("sizeof(Person): %d\n", sizeof(Person)); //-> 16 bytes
    Person *person = (Person*)malloc(sizeof(Person));
    printf("sizeof(person): %d\n", sizeof(person)); //-> 8 bytes ( Why 8 and not 16? )

    char buffer[32];
    printf("sizeof(buffer): %d\n", sizeof(buffer)); //-> 32 bytes
    person -> name = (char*)malloc(sizeof(buffer));
    printf("sizeof(person->name): %d\n", sizeof(person->name)); //-> 8 bytes ( Why 8 and not 32? )

    return 0;
}

I know that sizeof(Person) == 16 because of padding, but I don't understand the following.
If sizeof(Person) == 16, why sizeof(person) == 8 and not 16?
And if sizeof(buffer) == 32, why sizeof(person->name) == 8 and not 32?

Johanna answered 1/12, 2020 at 13:52 Comment(10)
person is 8 bytes cuz it's a pointer and the same goes for person->name.Megen
@PSkocik That is practically an answer. Will you make one? If not, why not? Would you mind me making one with basically the same content?Chiromancy
@Chiromancy Feel free to copy it and post it as your answer, lol :DMegen
Not "copy"; I said "basically same content". Trying to do a little more explaining and formatting.Chiromancy
I feel sure there's a 'classic' answer (with 384 upvotes) to this. But I've search and can't find it.Petrifaction
Aside: please use %zu to match sizeof. Again, it's a matter of size, %d might expect a 32-bit argument, but sizeof might supply a 64-bit value.Metanephros
@WeatherVane if I use %zu I get a warning: unknown conversion type character 'z' in format [-Wformat=]| Johanna
You'll perhaps need a more recent compiler :-) If you don't have %zu I suggest using %llu and cast the operand: (unsigned long long)sizeof(buffer)Metanephros
@BUG That means your compiler's obsolete in regards to this feature (your libc probably too). You can also cast to the sizeof expression to (int) and keep the "%d" (for these guaranteed to be small sizes, int is definitely fine, long long unsigned + "%llu" should be safe in a more context-free fashion).Megen
@PSkocik that cast seems to work fine for me for now - no warnings or errors. I will try in the future to update my compiler to be able to use %zuJohanna
B
3

For starters to output an object of the type size_t you shall use the conversion specifier zu

printf("sizeof(Person): %zu\n", sizeof(Person));
                        ^^^   

I don't understand the following: If sizeof(Person) == 16, why sizeof(person) == 8 and not 16?

The name Person denotes the structure

typedef struct person{
    char *name;
    int age;
}Person;

An object of this type occupies 16 bytes.

The name person declared like

Person *person = (Person*)malloc(sizeof(Person));

denotes a pointer. This pointer occupies 8 bytes and points to a memory allocated for an object of the type Person that occupies 16 bytes.

That is sizeof( Person ) and sizeof( Person * ) that is equivalent to sizeof( person ) are two different expressions.

And if sizeof(buffer) == 32, why sizeof(person->name) == 8 and not 32?

Again the name name has a pointer type and occupies 8 bytes. It is a data member of the structure person declared like

char *name;

This pointer points to a dynamically allocated memory that occupies 32 bytes.

Pay attention to that the size of a pointer does not depend on whether it points to a single object or to the first element of an array. That is you can allocate memory for a very big array but nevertheless the size of the pointer pointing to the allocated memory will not be changed depending on the size of the allocated memory.

Consider for example

int a[10];

int *p = a;

int b[10000];

int *q = b;

In this example the pointers p and q have the same size. You could write for example

int a[10];

int *p = a;

int b[10000];

p = b;

The size of the pointer p will not be changed after the last assignment.

Bodega answered 1/12, 2020 at 14:8 Comment(0)
E
6

Because, as sizeof(char *), sizeof(person) returns the size of a pointer. And in your case, a pointer to a struct (here to a Person struct) is of size 8.

And sizeof(person->name) returns the size of a pointer on a char as name is defined as a char *.

buffer is not a pointer, it is an array. The compiler knows it and sizeof(buffer) returns the size of the array, even through there is some similarities between the name of an array and a pointer, they are not treated the same.

Everetteverette answered 1/12, 2020 at 13:58 Comment(0)
B
3

For starters to output an object of the type size_t you shall use the conversion specifier zu

printf("sizeof(Person): %zu\n", sizeof(Person));
                        ^^^   

I don't understand the following: If sizeof(Person) == 16, why sizeof(person) == 8 and not 16?

The name Person denotes the structure

typedef struct person{
    char *name;
    int age;
}Person;

An object of this type occupies 16 bytes.

The name person declared like

Person *person = (Person*)malloc(sizeof(Person));

denotes a pointer. This pointer occupies 8 bytes and points to a memory allocated for an object of the type Person that occupies 16 bytes.

That is sizeof( Person ) and sizeof( Person * ) that is equivalent to sizeof( person ) are two different expressions.

And if sizeof(buffer) == 32, why sizeof(person->name) == 8 and not 32?

Again the name name has a pointer type and occupies 8 bytes. It is a data member of the structure person declared like

char *name;

This pointer points to a dynamically allocated memory that occupies 32 bytes.

Pay attention to that the size of a pointer does not depend on whether it points to a single object or to the first element of an array. That is you can allocate memory for a very big array but nevertheless the size of the pointer pointing to the allocated memory will not be changed depending on the size of the allocated memory.

Consider for example

int a[10];

int *p = a;

int b[10000];

int *q = b;

In this example the pointers p and q have the same size. You could write for example

int a[10];

int *p = a;

int b[10000];

p = b;

The size of the pointer p will not be changed after the last assignment.

Bodega answered 1/12, 2020 at 14:8 Comment(0)
C
2

As mentioned in a comment by PSkocik.

person is a pointer, same size as pointer to char. same for person->name.
struct person is a type, same size as Person.

Chiromancy answered 1/12, 2020 at 13:59 Comment(0)
P
2

This is a core concept in C. It's your breakthrough moment when you get it things start making sense.

A pointer is an address. I can have the address of your house written on a piece of paper and the paper doesn't need to be the size of your house.

I'm not being silly the way we use 'address' in C is closely associated to that real-life concept. But C pointers (addresses) are places in memory. They're represented by the value of some fixed number of bytes.

OK, so the postal system identifies your house by block of characters.

In computers we identify locations in memory be a sequence of bytes. Yes, we use bytes to store the location of other bytes.

malloc(sizeof(Person)) return the address of (a pointer to) a block of memory large enough to hold a Person structure.

It didn't return the block. That's 'somewhere' identified by, can be 'found', accessed, written to and read from using the address.

An pointer/address isn't the thing it's a reference to a thing. Here using reference in the broad computing sense not the slightly narrower sense it's used in C++.

Your machine clearly uses 64-bit addressing. There are 8-bits in a standard byte and and a size of 8 amounts to 64 bits.

The C Standard in some parts talks about pointers functionally. It tells you applying & to suitable expression returns a pointer that can be de-referenced by * or -> to get to the thing or part of it respectively. But it also talks about the 'address-of' operator (being &) and I believe why it uses ampersand which is a stylised 'a'.

To answer the questions. sizeof(person) is 8 bytes because that's the address of 16 bytes that malloc() has set aside for you to store 16 bytes as requested (the size of a Person object).

The answer regarding person->name is similar. It's the address of 16 bytes not itself 16 bytes.

Add printf("person: %p\n",(void*)person); it will print out the address. It's implementation defined how it comes out but it's almost certainly going to be like person: 0x7ffca93ce784 (but a different hex value). That won't change if you change the values in it (e.g. person->age=50;).

Do take heed of that advice that you need to use %zu to output a size. Also get into the habit of calling free(person); when you've finished with memory you allocated. Render unto free() anything malloc() rendered unto thee. If not that memory you were allocated can't be reused. That won't affect this little program. But it will become an issue later.

I'd say formalising the distinction between a reference and its referant is a key Computing notion. The something that identifies a thing and the thing. No one has an issue that their name refers to them but isn't them. But somehow when it all gets abstracted into computing you need to consciously understand the distinction more clearly.

All programming languages have this and useful ones have some notion of pointer-like things. In Java it's an Object Variable. That's a reference to an object. Not the object. It even lets slip and if you abuse want gives you a NullPointerException. But I thought Java didn't have pointers! Secret.


The question you'll ask soon is why sizeof(Person) is 16 but sizeof(char*)+sizeof(int) is 12. What (you will ask) are the other 4 bytes doing? I won't go into it but there may be 'dead' bytes in a structure to make things line up nicely. Many computers need (or work more efficiently) if things like pointers and ints are stored at addresses with some low bits set to 0 - it's called alignment and it's a hardware thing!

Petrifaction answered 1/12, 2020 at 15:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.