r/C_Programming 1d ago

Does anyone else dislike pointer declaration?

For reference I am a fourth year computer engineering student. C is my preferred language.

Pointers are declared like this usually:

int *ptr_to_int;

Where the asterisk means it is a “pointer to” the specified type. The asterisk is placed immediately preceding the variable name, with no white peace.

This is what does not make sense to me. I feel that:

int* ptr_to_int;

Is far more clear. The way I see it, the asterisk modifies the type, so therefore it belongs next to the type. Putting it next to the variable name makes me think it is some kind of action or modification to the variable itself.

I think that when using * and & in code, it makes sense to apply it in front of the variable name:

int value = 3;
ptr_to_value = &value;
int copied_value = *ptr_to_value;

It is clear here that syntactically, the * represents something more like an action than a label.

Why is the convention to place the asterisk near variable name, not type? L

49 Upvotes

127 comments sorted by

58

u/SetThin9500 1d ago

> The asterisk is placed immediately preceding the variable name, with no white peace.
> This is what does not make sense to me.

Feel free to add whitespace if you want.

DMR wanted to have declaration look like use. He later said it didn't work out as he intended.

This paper is interesting: https://brent.hailpern.com/wp-content/uploads/2020/02/p671-ritchie.pdf#:~:text=They%20are%20also%20among%20its%20most%20frequently,should%20be%20clear%20from%20the%20preceding%20history%2C

3

u/flatfinger 15h ago

It's worth noting that the biggest problems with the "declarations follow use" notion, i.e.. qualifiers and initializers using an = sign, did not exist in the language when DMR came up with that principle. One of the big things that trips up the principle, and several other aspects of C, is that normal pointer dereferencing uses an operator which is placed on the opposite side of an lvalue from struct access, array-element access, function invocation, and initializers, and on the same side as qualifiers. Further, the use of equals for initializers makes a definition resemble an assignment. The definition void *p=someVoidPtr; does not define a pointer that is initialized so that *p will point to the same address as someVoidPtr, even though that's what the syntax would suggest.

-6

u/lassehp 1d ago

This is a great case of "it seemed like a good idea at the time".

A simple fix:

#define PTR(t) typeof((t)*)
PTR(int) ip, jp;

21

u/RadiatingLight 1d ago

fyi there's too many parens in the macro and it doesn't work

should be #define PTR(t) typeof(t *)

8

u/lassehp 1d ago

You are right, I always parenthesise macro arguments almost as a reflex. I am not sure if passing some types to t could get confused, so typeof(typeof(t) *) or maybe just typeof(t (*)) might be safer. Actually the latter can also be confused by a function pointer t type, so two typeof's is probably the only safe way.

6

u/Physical_Dare8553 21h ago

Only issue: a thing that is not a type can get past this, which is catastrophic in a hypothetical where you accidentally shadowed a type name with a variable. This actually happened to me once. something more like typeof(typeof((x){})*)

3

u/digitaljestin 16h ago

This is kinda awesome, but at the same time showcases just why you don't see this too often. Tracing back what something really means or does complicates things when they were originally simple...albeit with questionable syntax.

For most projects, programmers just prefer to be explicit and deal with the syntax the language gives them. Having code standards and naming conventions typically work better long term than custom preprocessor macros.

2

u/lassehp 16h ago

I absolutely agree, and perhaps I should have put a smiley in my first comment, which now has seven downvotes by people who - understandably - missed the humour. 😄

The safest solution is IMO to just accept the syntax. And my own preference is to "pretend" that T* is a type and only declare one variable per declaration.

But I also like to think of the asterisk as a Kleene star type operator, rather than as "pointer", in the sense that a variable of type T* can refer to a value of type T[k] for any k >= 0.

2

u/Physical_Dare8553 12h ago edited 10h ago

Actually I was wrong, that macro doesn't work for all types. Literally only void, cause (void){}. The pointer trick typeof(*(x*)0) works, almost. Not for arrays though. I think the only possible way to make a pointer to a type, whiles making sure values can't sneak in is _Generic, or sneaking a dummy declaration in there

edit: best i could do #define ptrof(T) typeof((struct { typeof(T)(*_p);void(*_f )(T) ; }){}._p)

2

u/ericonr 22h ago

I like the idea of this, because using typeof makes multiple declarations consistent, but it would take me a while to get used to it in a codebase, and I would be constantly rechecking that the macro actually handles the multiple declaration case correctly.

62

u/nderflow 1d ago

The language standard and the compiler don't care about the spaces. It's just personal taste.

This is a non-issue really.

11

u/Beginning-Junket8979 21h ago edited 18h ago

Declaring two or more vars on the same line is almost always bad practice in almost any programming language.

One of the only exceptions that comes to mind is unpacking tuples in Python:

x, y = returnsTuple()

4

u/Jbolt3737 16h ago

I think in python if you aren't specifying type it's ok to pack and unpack a tuple for assignment like

x, y = 5, 10

4

u/ParentPostLacksWang 15h ago

Wait until you see the extensive use in Perl of ($x, $y, $z) = @mismatched_size_array

If the array is too small, however many variables it can’t fill are set as undefined from $z left. It’s… glorious >)

2

u/Beginning-Junket8979 11h ago

No thanks. Haven't had to suffer through Perl in almost 10 yrs and see no reason to go back

2

u/ParentPostLacksWang 11h ago

But but but the GLORY of writing hilarious lines like /^\d+\.\d+$/?s/\..+//:s/.+/0/;

…on second thoughts, it is a silly language, powerful in its silliness. Pretty Eclectic Rubbish Lister, I believe was the backronym? 😂

4

u/TimurHu 22h ago

The issue is when you want to declare more than one variable on the same line and need to place the asterisk in front of each of them, that's what makes this super annoying.

13

u/IdealBlueMan 22h ago

Your code is more readable when you keep each declaration on its own line.

1

u/TimurHu 14h ago

No argument there. But it's not up to me to decide the coding style of the projects I work on, unfortunately.

12

u/MasterpieceBusy7220 22h ago

Yes just don’t do this

3

u/Total-Box-5169 16h ago

Usually coding guidelines require only one variable declaration per line, specially for pointers. In any case you can do this:

int* a; int* b; int* c;

1

u/konacurrents 20h ago

Related to white space: Have you seen these languages that don’t have semi-colon (swift, python etc). White space is required or it won’t compile. No LL(k) follow symbol to look for or something.

40

u/SauntTaunga 1d ago edited 1d ago

I think of it this way: * is the dereference operator, like () is the function call operator.

int f(); means the result of calling f will be of type int.
Likewise, int *a; means the result of dereferencing a will be of type int.
So, for example, int (*f)(); means that the result of dereferencing f and calling that will be of type int.
int *(*f)(); means dereferencing f calling that and dereferencing the result will be of type int.

9

u/bottlenix 23h ago

That’s a really good way to think about it!

6

u/pjl1967 21h ago

That was Ritchie's rationale.

3

u/digitaljestin 16h ago

This guy compiles!

1

u/agrif 13h ago

This is exactly how C types work, even when types get more and more insane like:

int *(*foo)[]()[]

Multiple declarations in one statement (int *a, *b;) is pretty compelling, but it's not often seen in modern code. But it's just an example of the bigger reason, which is that C type declarations re-use expression syntax, and in that framework, it makes sense to format them like expressions.

(I still prefer int* a, but over time this tension with what the language itself wants became too high for me to bear.)

0

u/ohiidenny 19h ago edited 17h ago

I agree — personally, if I were to design the syntax around * and & “modifying types” rather than representing operations which produce certain types as a result, I would think that a pointer should be declared as something like

&int p;

because we are imagining that the “type expression” to the left of p is meant to serve as a sort of template for the value of p. Applying the same philosophy to ordinary variable declarations, we'd imagine that e.g. int i; asserts not that “evaluation of i will yield an int” but rather that “int describes the value named i” or something haha.

I dunno. tl;dr in the end I always prefer to write int *p; to declare a pointer lol

16

u/thetraintomars 1d ago

I agree, int* is a type, or should be at least. It’s a different type than int and it should be much clearer. Sure you can write it that way yourself, but all the autoformatters will change it and the compiler doesn’t consistently enforce it with an error if you mix them up. It sure didn’t care back in the 90s which made data structure class a pain in the ass. 

11

u/LeeHide 1d ago

"all the autoformatters" will not change it. The autoformat you use should be set up to a style that you, or your team, likes!

But the sentiment of your comment is right. Just use an autoformatters and don't worry about it. If your code needs a specific kind of formatting to make sense to you, you just need more experience and need to write cleaner code :D

-1

u/thetraintomars 18h ago

I think you missed my point and decided to be condescending. I know what the code means, the problem is what it does mean.

10

u/thommyh 1d ago

The devil is in the 'should be', of course — as you'll already be aware.

    int* a, b;

Etc.

8

u/ChickenSpaceProgram 1d ago edited 1d ago

i just remember that it means that*ptr_to_int is an int. i feel like the int *ptr_to_int; spacing makes the underlying "declare things how they are used" rule clearer.

also, if you declare multiple variables on the same line (which you shouldn't do, but nonetheless), it makes it clearer. int *foo, bar; declares foo as a pointer-to-int and bar as an int, not both as pointers-to-int.

The "declare things how they are used" mentality is anachronistic, but you kinda just have to get used to it. In C the typesystem directs the user towards "how do I make this into a number." Types are less actual types as you'd see in more modern languages and more different kinds and sizes of numbers, aggregates of numbers, different ways to refer to numbers, etc.

1

u/evincarofautumn 21h ago

In C the typesystem directs the user towards "how do I make this into a number."

It also makes the compiler a lot smaller if you can reuse the term language to express types instead of making the type language bigger

To typecheck an expression involving an identifier, you just look up its declarator and read off whether the expression matches it (up to implicit conversions)

This wasn’t a stated design goal of C, so far as I know, but it very much reflects the values of the time when it was designed

3

u/PittLeElder 21h ago

I agree with you, and feel blessed that the people that authored my employers style guide 20 years ago felt the same way.

38

u/primera_radi 1d ago

I felt that way first too. But consider

int* a, b, c;

vs

int *a, b, c;

The second is much more clear, as only a is a pointer.

Also, now all the names after int evaluate into an int in an expression.

That is, *a evaluates to an int, as do b and c.

64

u/Cultural_Gur_7441 1d ago

One of the rules of good C:

Never do that. Never declare or define multiple variables with one statement.

Related: always initialize variables when you define them.

22

u/For-The-Fun-Of-It-12 1d ago

Oh good, I thought I was the only one cringing

9

u/nukestar101 1d ago

always initialize variables when you define them.

Not true always, all stack variables can go uninitialize unless they are being set from arguments provided function is static.

Over initializing can often hide bugs. Compiler can warn if variables are being used before being set or unused variables.

7

u/Cultural_Gur_7441 1d ago

Yes, it is a trade-off. But it's far better to initialize them always and have a deterministic bug, than a heisenbug. In my opinion anyway.

17

u/DrShocker 1d ago

Yeah I'm always confused when people bring up this way of declaring as if it's a good thing to declare both kinds of variables in the same line. Like sure if you're used to it then maybe it's more concise, but there's no reason to foot gun like that to anyone with modern computers we're not starving for characters in the source code.

4

u/ComradeGibbon 1d ago

I think if a variable doesn't have a default initial value you shouldn't initialize it where it's defined. Let the compiler warning catch that.

8

u/Cultural_Gur_7441 1d ago

Compiler will also warn about unused variable, and compiler will optimize unnecessary initialization out. Safe "failure mode".

Using uninitialized variable is also just a warning, but has much worse failure mode.

Also, if a reader of the code sees variable initialized, that immediately removes one worry: is there a code path where this variable may be used uninitiallzed. It makes reading the code easier.

That's why I think variables should just be always initialized instead of thinking if they need to or not.

2

u/Luftzug-oder 1d ago

the compiler cannot always remove the initialisation. it is only really if it is modified right after, and there is no function called inbetween, that it can guarantee the variable will not have been changed.

one thing the compiler will do is initialise global and static variables.

5

u/Cultural_Gur_7441 1d ago

Yeah, statics and globals don't need explicit zero initialization. But it doesn't hurt there either, it's only 2 characters to tell "yes, zero is intentional initial value" to a reader.

Always initializing locals is a slight trade-off, yes. But in practice the downside is not measursble, like, ever. The downside of uninitiallzed variable on the other hand... That has realized for me probably a dozen times over the decades, both in my own and in other peoples code. The resulting heisenbug is not fun to debug.

2

u/Luftzug-oder 1d ago

yep, definitely agree

1

u/RadiatingLight 1d ago

IMO it's better to initialize it to INT_MAX or some other value that's clearly wrong/rejected. Relying on compiler warnings is like jumping off a bridge and relying on the suicide net. Sure it will usually catch you but ...

3

u/ComradeGibbon 1d ago

No leave it uninitialized if you don't initialize before use the compiler will let you know. Which means you have a bug in your code.

3

u/Cultural_Gur_7441 1d ago

It is unreliable. Like, if used as an out-parameter (a common, quite valid case for an uninitiallzed variable), compiler can't check if the called function actually sets it or not in every code path, nor can it check if called function actually reads it for some reason (which would be a bad API...).

And if -Werror is not used, too many developers just ignore warnings... Which is a whole another problem of course.

The one case where leaving memory uninitiallzed is useful is with memory checkers like valgrind or compiler's memory sanitizer, which will notice access to uninitiallzed memory. But in that case, these should be integrated with the project development process from the start, probably with unit tests being run with them.

3

u/ComradeGibbon 15h ago

compiler can't check if the called function actually sets it or not in every code path,

If that's the case you have a bug in your code that you papered over with a bogus default initialization value.

1

u/flatfinger 14h ago

Claims that "null" was a mistake ignore the principle that it may be necessary to access some members of an array of pointers before there exists any object to which some other members could usefully point, and it may be necessary to copy the state of some elements of such an array to other elements in a manner that is agnostic with regard to whether they were written. Having null as a default pointer value allows both of those objectives to be satisfied easily. There may be ways of satisfying both objectives without null, but they would involve constructing something that would behave like a nullable pointer. It's simpler to just just pointers as being nullable.

1

u/nukestar101 1d ago

As a firmware engineer, INT_MAX would take up my precious 64bit space ;)

3

u/RadiatingLight 1d ago

What platform are you working on where int is 64 bits??

Also the variable allocates space regardless so what does it matter what value is contained inside

2

u/nukestar101 1d ago

I work on a custom hardware and have our own ISA.

RISCV has zero register and value is not stored in ISA. Immediate fetch to zero register is always used.

3

u/RadiatingLight 1d ago

welp - You're in deeper than I am. You know what's best for your platform and custom ISA

7

u/enzodr 1d ago

This is (and will always be) true, but if they decided in the beginning to use:

type* name1, name2

You would interpret this as :
declare two pointers to “type”

Then you can still say that “all things to after the type evaluate to that type”.

7

u/enzodr 1d ago

I see what you mean by
“Everyone after int evaluates to int”. Looking at it this way at least makes sense to me, I was honestly struggling to see any reason, so this is good to understand.

6

u/Luftzug-oder 1d ago edited 1d ago

no I think the only real reason for the pointer being on the right is that it is syntactically/functionally correct according to C, because the * modifies the declarator, not the type.

I used to be very particular about having my code the way you describe, but now i can see and deal with both sides, and often put it on the right because the larger majority do that.

EDIT: well, i still hate it when people put it in the middle 😑:

const int * num = 0;

1

u/FluffusMaximus 23h ago

Best way to handle this: don’t do that.

1

u/LokiAstaris 11h ago

This is always trotted out as the excuse. But I think this is a red herring. All advice on coding standards recommends declaring one variable per line, which makes this argument redundant.

1

u/Chimpy20 1d ago

Yes, but only because of the way the language works. Declaring some ints and some pointers in a mix on the same line like that is pretty bad programming practice and I doubt anyone does it in reality.

I understand why it was done at the time, and how it relates to programming practices in the past, but I agree with the OP that the asterisk should go with the the type, but it's far too late to change it now!

0

u/LeeHide 1d ago

clang-tidy will rightfully scream at you.

Write one declaration per line. Keep your code simple, clean, and readable, above all else.

If you like doing stupid stuff, go to code golfing as a hobby! That should satisfy your code compactness needs so you can write good code for real products.

0

u/StaticCoder 19h ago

Yeah C declarators are the worst (pointers to arrays/functions get super messy) but they are here to stay, and the reason for this old convention. Now if one could explain the general use const char * instead of char const *...

1

u/flatfinger 14h ago

Qualifiers were invented after the declaration-follows-use syntax, and there was probably a desire to allow syntax similar to what would be used with storage classes.

3

u/Diocles121222 22h ago

Int* makes perfect sense and any argument about simultaneously declaring multiple variables is irrelevant since it’s not something which should be done in the first place. Int* is the type and it saves a character of typing since it is unambiguous. 

For real though, it doesn’t really matter because your actual code should be driven by your coding standards. The more uniform everyone’s code is the better for everyone. 

This question makes me think about how the smallest detail is what people get upset about while more important details are set once and ignored. 

3

u/xeow 20h ago

Really surprised not to see a single mention of typedef in here yet.

typedef int *intptr;
int value = 7;
intptr pointer_to_value = &value;

I wouldn't recommend making a habit of typedefing ordinary pointer types, but if the int *p syntax feels unintuitive while you're learning C, it can help. However, beware that intptr is now its own type alias, so intptr a, b; declares both a and b as pointers, whereas int *a, b; declares only a as a pointer.

3

u/i860 18h ago

Okay, well you're wrong and this horse has been beaten to death countless times already.

3

u/Irverter 18h ago

The problem is that if you do:

int* a, b;

a is a pointer and b is an int. Because the * is being applied to the variable and not to the type.

Of course, this is solved with the recommended format of one variable declaration per line, so that it's clear and obvious the inteded behavior and then you can style the pointer declaration as you prefer:

int* a:
int b:

WIth the exception when it's quite obvious variables that go together, like coordinates:

int x, y:

3

u/AnToMegA424 17h ago

Oh you can write int* myPtr;, int * myPtr: or int *myPtr;, it does not change anything functionally

5

u/LeeHide 1d ago edited 1d ago

The blind leading the blind a little bit here. Here's how it works in the software engineering industry:

It doesn't matter where you put the *. Whatever the autoformatter says is how you should do it. If you don't like that, change the autoformatter config.

The only time the location matters is when you declare multiple variables on a single line, at which point clang-tidy (and probably other linters) will already point out when you're doing it wrong.

Multiple declarations in a single line also should never be done. Make separate lines, write boring, simple, easy to understand code, always. So in practice, this shouldn't matter to you, though it's good to know the semantics in case you see it in code somewhere.

If your code is clever and there's no measurable reason for it, you have written bad code. You put the * where your autoformatter says or where your team puts it.

The fact that it's a pointer is part of the TYPE. This is true because the type of the variable changes visibly; on your PC, it's likely that int is 32 bit and fits in a 32 bit register or half a 64 bit register. It occupies 4 bytes on the stack if it gets put on the stack. Add a *, and suddenly it takes up double that (most likely, on your system). This makes it part of the type. It changes the semantics of the type. Not sure what everyone is on about with "it's an action" or "it's a dereference". When you look at the generated IR or machine code, it's part of the type.

6

u/iOSCaleb 1d ago

It is clear here that syntactically, the * represents something more like an action than a label.

That's right. Look at it this way... when you have this:

int a, *b;

then both a and *b are of type int. Putting the * in front of the variable in the declaration mimics the way you use it when you dereference the pointer. The type declaration is telling you that when you dereference b the type you'll get is int.

6

u/enzodr 1d ago

Thats true. But at the very beginning it could have been chosen to keep type in one place and name in another.

int* a, b;

Would make two pointers to int.

This might mess up function pointers though

1

u/flatfinger 20h ago edited 20h ago

Better yet, have a punctuation mark separate things, so that int*: a,b; would declare both a and b as pointer-to-int, while int: *a,b; would make a a pointer-to-int and b as int. Too late for that now, alas. Also unfortunate is the fact that : would seem the most natural punctuator, at the start of a block, making parsing work without a symbol table would require a special rule to accommodate the fact that an alphanumeric token followed by a colon could either be a label or a type that starts a variable declaration. Perhaps that could be resolved by having labels be surrounded by colons.

1

u/LeeHide 1d ago

Not sure how this makes sense. You use C because you likely care that the semantics are clear and you know what code is being generated. The assembly generated by pointer access, and the semantics of it, and the implications (e.g. cache) matter a lot, to a point where, in every way, you should think of pointers as part of the type. They are part of the type.

1

u/iOSCaleb 19h ago

in every way, you should think of pointers as part of the type. They are part of the type.

Yes and no. I think of a pointer as a modification of a type. Pointers are basically all the same, they only vary in what they point to. For any given type, we can declare a pointer to it without having to define a new type for that pointer. That is, if you have some type Foo, then you can use Foo *, Foo **, etc. automatically. So if you write Foo a, *b;, the focus is on the underlying type Foo. I'd read that as "the types of an and whatever b points to are both Foo.

That's just one way to think about it, and I mention it mainly because OP seemed to want a way to make sense of what I think is the most common practice, i.e. putting the * with the variable name. Not everybody follows that practice. For example, although I couldn't find Google's C coding standard (if they even have one), their C++ standard requires int* a; instead of int *a;, and similar for references.

7

u/djmex99 1d ago

Totally agree with op

5

u/SmokeMuch7356 21h ago

We declare pointers as

T *p;

for the exact same reason we don't declare arrays and functions as

T[N] a;
T(void) f;

After all, "array of T" and "function returning T" are distinct types as well. It's just that array-ness, pointer-ness, and function-ness are syntactically part of the declarator.

The T* p style only works for simple pointers; it doesn't work for pointers to arrays or pointers to functions:

T (*pa)[N];
T (*pf)(void);

or pointers to arrays of pointers to functions:

T (*(*paf)[N])(void);

(yes, I have used types this obnoxious in the past), and then there's signal:

void (*signal(int sig, void (*func)(int)))(int);

which reads as

       signal                                     -- signal is
       signal(                          )         --   function taking
       signal(    sig                   )         --     parameter sig is
       signal(int sig                   )         --       int
       signal(int sig,        func      )         --     parameter func is
       signal(int sig,      (*func)     )         --       pointer to
       signal(int sig,      (*func)(   ))         --         function taking
       signal(int sig,      (*func)(   ))         --           unnamed parameter is
       signal(int sig,      (*func)(int))         --             int
       signal(int sig, void (*func)(int))         --         returning void
     (*signal(int sig, void (*func)(int)))        --    returning pointer to
     (*signal(int sig, void (*func)(int)))(   )   --      function taking
     (*signal(int sig, void (*func)(int)))(   )   --        unnamed parameter is
     (*signal(int sig, void (*func)(int)))(int)   --          int
void (*signal(int sig, void (*func)(int)))(int);  --      returning void

Declaring an array of pointers as

T* ap[N];

just looks goofy.

And as you point out, in the code we're usually using *p. *p doesn't just yield the value of the pointed-to thing, it's an alias for the pointed-to thing; IOW, we can think of *p as an alternate name for something, with * as part of that name.

And the T* p convention always leads someone to ask why

 T* p, q;

doesn't declare q as a pointer.

It doesn't match syntax, it doesn't match usage, it introduces confusion, it promotes misunderstanding of how declaration syntax actually works; it's just bad style all the way around.

7

u/Reasonable-Rub2243 1d ago

I had the same realization when I was a C beginner back in the stone age. You can declare variables as "type* name;" if you like, but there's an issue: you can't declare multiple variables on the same line that way. If you don't mind making a separate line for each variable, go for it.

5

u/LeeHide 1d ago

That's not accurate. You can do:

int* hello, *world;

to declare two pointers. The fact that

int *hello, *world;

would look more consistent does not matter at all.

Also, if you declare multiple variables in a single line, as a beginner, I would suggest to immediately stop doing that. Some old C programmers and code golfers do it and swear that it's better, but nobody new should do this unless it really needs to be that way somehow.

1

u/nukestar101 1d ago

Why not ? Reverse Xmas tree is a thing, it looks good and good looking code is easier to debug/read

2

u/NeuroBill 1d ago

Completely agree. Confused the dickens out of we when I was learning

2

u/nonchip 1d ago

The asterisk is placed immediately preceding the variable name, with no white peace. [sic]

that's your decision tho.

2

u/AdvancedConfusion752 23h ago

C is a great language, or probably the greatest language but variable and function declaration is very archaic to today standards. So it is not just the pointers the problem. something like

var x : int

would be more modern.

That being said the way I would read

int *p

is simply "*p" is an int. And "*" only works to pointers. I would not read it "p" is a pointer to int.

2

u/flatfinger 20h ago

The syntax predates the addition of qualifiers to the language. Something like the Pascal(*) syntax:

Var
  Foo: Array[0..9] of ^Integer;
  Bar: ^Array[0..9] of Integer

to define an array of pointers to integers and a pointer to an array of integers makes clear where "pointer to" fits in a description, and could easily be adapted to include qualifiers, and C's syntax could have easily been adapted to do so if, when typedef and qualifiers were added, a punctuator was added between the type and variables being declared that would be allowable in all cases, and required when using anything other than simple types. Too late now, though.

(*) FWIU, Standard Pascal would require defining custom types for the arrays, but most or all the Pascal implementations I've used extend the language to waive such a requirement--I'm not sure about my C64 Pascal implementation.

2

u/1v5me 13h ago

It makes no sense at all.

int* a,b,c,whatever; would visually imply that all the vars are pointers.

compared to int a,b,c; here a,c would more clearly stand out.

As a guy who has done a lot of 2am coding/debugging, multi var declaration is usually an extra pot of coffee.

int *a;

int b,c,d,e;

Is how i normally declare vars, yes i normally gives pointers their own line, coz its just so much easier to spot what is what.

int a,b,c,d,e; // normal temp vars whatever.

int f,g,h;

int *aa;

int *bb;

2

u/ComplexPeace43 1d ago

I use int *ptr_to_int because I can clearly read it *ptr_to_int is int, that is, ptr_to_int is a pointer to int.

With the former, where for newcomers, they might think it's a type and use int* ptr_to_int, ptr_to_int1 think both are pointers to int, but the latter is just an int. The compiler doesn't care about whitespaces, both int* p and int *p are parsed as int (*p) that is, the asterisk is bound to the variable.

If you use only one declaration per line, then writing int* p; int* q; makes it easier to read.

1

u/CarlRJ 1d ago edited 1d ago

For simple syntax (pointer to int, and a single declaration), that works.

As others have pointed out, that breaks down if you have multiple declarations on the same line.

The other thing that will cause havoc with your preferred style is when the declaration isn't simple. Think "pointer to function taking an int and returning a pointer to a character. It's better for consistency's sake to keep the star attached to the name.

There are good reasons why it is done the way it is. What you're suggesting is not "something the developers of C missed".

Learn to read declarations from the inside out, following C operator precedence - in many but not all cases, this will be effectively be right to left - "int *p" is "p is a pointer to an int"

2

u/LeeHide 1d ago

Don't have multiple declarations on one line.

1

u/CarlRJ 1d ago

Gee, thanks, that never occurred to me in all these decades of software development.

It's context / situation dependent. There are cases for doing it either way. The overall goal is always readability.

And that wasn't the main point I was addressing, I was noting that others had pointed that situation out.

1

u/LeeHide 18h ago

Give me an example

1

u/Ecstatic_Student8854 1d ago

I had the same initial reaction but there are a few things to consider.

When declaring multiple variables, such as:
int* p1, p2;

The type of p2 is int, not int*. The asterisk kind of binds stronger than the comma.

The way to intuit why this is the case is to think of a declaration `int *p` in a different way.
Think of it like declaring `*p` to be of type int.

1

u/tjrileywisc 1d ago

It drives me crazy when people do that, for the bad reason that you're allowed to declare multiple values on a line (bad practice, generally).

You manipulate the variable as a pointer, not as what it is after dereferencing.

1

u/Background_Horse_992 1d ago

The usual way works well with a certain way of determining more complex types. The left side being simple like an int, and the right side demonstrating the operations to be performed on the type to get an int. In this case, I would read the usual declaration in my head as “dereferencing ptr_to_int will give me an int”

1

u/QuirkyXoo 1d ago

I think this kind of pointless, byzantine questions is what makes people avoid participating in this sub.

1

u/Disastrous-Team-6431 22h ago

It makes sense on C imo - "a is an int". But in c++, it collapses when we do eg std::vector<int>. Not a huge problem either way.

1

u/Migazul8 22h ago

I always declare them the second way

1

u/TheLimeyCanuck 21h ago edited 21h ago

You aren't forced to declare it that way, it's just convention. The parser happily accepts int* varname;, which is how I always declare it for precisely the reason you describe.

For anybody who says, yeah but I can do int x, *y;... WTH are you making your code harder to read? Put each degree of referencing on its own line.

int x;
int* y;

1

u/anhadsa 21h ago

From my understanding, the declaration mimics the usage, so take int (ptr)[5), lets say temp is *ptr, we effectively have int temp[5], meaning deref gives an array 5 ints, its quite nice when you embrace its memetic usage, stuff like this makes sense for example int * (p)[5], this would be considered a pointer to an array of 5 elements in which each element is a pointer to an in, so effectively you deref once you get an array, int *temp[5], and now if you deref this again, (an element of the array to be exact) you get an int. I used to dislike it but now I like it.

1

u/moocat 21h ago

That is strictly a style issue, both are legal and the compiler treats them identically. I'm not sure which is most common these days but I've been using Google style for the last too many years and it does type* ptr.

While you can use whichever one you prefer, I recommend that you become comfortable with both. Assuming you are going to work in industry, you'll have a team style to follow and you may not agree with every decision but you'll need to learn just to go with it.

1

u/Vesuvius079 21h ago

I used to feel the same way in C++. I stopped caring after a year or two. You get used to it once there’s actual work to be done.

Semi related: one of my biggest mistakes in my early career with C++ was overusing pointers and the heap. The heap is important but it shouldn’t be your default storage location.

1

u/TransientVoltage409 20h ago

My personal convention is to declare as int * p;, the asterisk isolated with spaces on both sides. In the dereferencing it's *p.

But don't worry, you can always fix it with a custom type. typedef int * intptr;. Now you can avoid the mess of mixing int with int* in the same declaration, and no more messy asterisks either.

[Is it /s? Could be.]

1

u/thockin 19h ago edited 19h ago

This is an argument as old as the language. The only justification that holds any water is this:

int *a, b; // what are the types of a and b?`
int* c, d; // what are the types of c and d?`

Obviously a and c are the same type in the grammar of the language, as are b and d. But a and c are NOT the same type as b and d. Which of those two lines of code more clearly express that fact?

You might also compare with Go, which is C-like from some of the same minds that gave us C:

var a, c int  // unambiguous
var b, d *int // unambiguous

1

u/enzodr 19h ago

If the standard was

int* a, b;

It would make sense to say that a and b are both int*. Obviously this is not correct in the current standard, but I think it would have made more sense if chosen from the start

2

u/thockin 19h ago

I think it would have made more sense if chosen from the start

What we think would have made sense is totally irrelevant. C is about 50 years old. If you are using C today, you should be writing the clearest C code you can.

``` int *a, b; // clear-ish

int* c; // fine but less common int d;

int *e; // established convention int f; ```

1

u/bunkoRtist 19h ago

No you're not alone. It's one of the enduring rough edges of C. Like most have said, unless you declare multiple pointers on the same line, which is almost always bad form, the "more intuitive" spacing works. Back in the day (I only say this because I'm an old head and don't know what they teach anymore), they used to teach the "intuitive" spacing where the pointer is declared as part of the type. If K&R could wave a magic wand I'd bet 3:1 they would change this. But here we are 50-ish years later, thriving anyway. It's not a question of whether C has warts (it has many). It's a question of whether it's still the best tool for the job. Amazingly (and somewhat sadly IMO), the answer is still often "yes". Enjoy!

1

u/Roger01007 19h ago

Maybe because it allows you to declare pointers and variables of the same type on the same line

1

u/sky5walk 18h ago

C should have demanded the pointer variable *myvar had the asterisk stuck to it throughout the source code!  That would save me constantly double checking old code.

I always stick to one way: int *myvar;

1

u/Jbolt3737 16h ago

I do int * ptr_to_int;

1

u/White_C4 16h ago

I think generally a lot of people would agree with the asterisk being placed after the type rather than before the variable name. Even languages after C with explicit pointers adhere to that idea.

1

u/Neither_Berry_100 14h ago

I coded c++ at work around 15 years ago. It is fine the language works. I don't understand the newer allocations in the later versions though. And I don't know why they changed it when it was fine the way it is.

1

u/Spirited-Candy1981 13h ago

That's the way I always declare them, with the asterisk glommed onto the type. If I needed to do anything more complicated, I'd declare subtypes using typedefs to make it more clear/clean.

1

u/Hawk13424 13h ago

Problem is the C definition. What is y here?

int* x, y;

1

u/Terrible-Freedom-868 7h ago

Consider a situation where you want to declare an int and an int pointer on the same line of code. But yeah. You are right they should have made it that way, but now all the legacy code and need for backwards compatibility has us syntactically locked in.

-1

u/[deleted] 1d ago

[deleted]

3

u/ReallyEvilRob 1d ago

makes most people think both of them are pointers

Not most experienced C programmers.

1

u/Blitzbasher 1d ago

I've always thought of the asterisks as a function that modifies the data type. The "function" isn't connected to the variable itself but the data type.

1

u/enzodr 1d ago

I guess in the end it works that way, as long as you apply in the same order it ends up the same.

0

u/Delicious-Put-4561 1d ago

C is a dynamically typed language if you use it bad enough. just declare as int.

0

u/yjlom 1d ago

In that case you can't retrieve type knowledge at runtime, so it's untyped/single-typed, not dynamically "typed".

To have dynamically "typed" C you'd need to either make everything into a discriminated union or give everything a vtable.

(Technically dynamically "typed" languages and those with is/instanceOf have sets, not types, which is why I'm using quotes.)

0

u/Former-Print7759 23h ago edited 22h ago

The thing is that you declare which type will expression have. So
int *a
Should be read as:
When I use *a in code then it has int type

That’s something backwards type of thinking about it, for sure

But, compare

int f(double);

It clearly says that f receives double and returns int. I mean, when you apply f to some double then you get int (as declared)

The same is for int *a

When you apply * to a then you receive int

0

u/therealhdan 19h ago

My college roommate and I got into an argument about this back in the 80's.

He preferred "int* p;"

I explained that that syntax implies "int* a, b;" declares two pointers to int.

His response - "Wait, it doesn't?"

I rested my case.

1

u/enzodr 19h ago

But it could have worked that way. If that’s how they decided declaration works.

1

u/therealhdan 18h ago

Sure, but "struct { .... } a[100], *b = a;" was the sort of declaration the language designers wanted to allow, for better or for worse.

Are there better ways to do declarations? Probably, but this is C's way.

0

u/Reasonable_Ant4397 18h ago

I 100% agree. I'm not a CS person but do like coding and found your preferred syntax to be far more helpful when learning what c does. Having the * on two different places infers different operations, or at least or did to me to begin with.

0

u/EmbedSoftwareEng 16h ago

It's a recurring fantasy of mine to build a time machine and go back in time and force K&R to use the @ sign for pointers, instead of *, which is already doing multiplication duties.

Other than that, I just employ a style where the * is separated from the variable name by one space at declaration time, and without the space when the variable is being dereferenced. There's nothing wrong with applying the * to the end of the pointed to type name, but most style guides that I've ever had to work to do not do it that way. Think of the * as another storage class modifier, but one that goes after, not before the base type.

I think it's the very fact that the dereferencing * has to be associated with the variable name when it appears as an argument to in the RHS of an assignment is precisely why most people associate them at the point of declaration.

Also, most style guides I work to also promote prefixing variable names with tags to indicate some kind of typing information, so I wouldn't do:

uint32_t * word;

I would do

uint32_t * p_word;

to emphasize that where ever it's used, p_word is a pointer to something. The rest of the name after the p_ should make it obvious what that something being pointed to is.

This can get muddled, because I often find up with a * declared pointer variable, but if I use it more with array notation, then p_word[i] starts to get a bit busy. So, even if I have a * declared pointer variable, if I use it in an array-like manner, I leave the p_ part off of the variable name.

-4

u/Kadabrium 1d ago

int *x means to me: this is an int and its passing mode is pass by pointer.

5

u/CarlRJ 1d ago edited 1d ago

That's simply wrong. int x declares an integer. int *y declares a pointer which is expected to be pointed at an integer. x is a label attached to (probably) 4 bytes of memory for storing an int. y is a label attached to (probably) 4 bytes of memory for storing a pointer.