Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I checked the section "Interlude: Arrays" because the relationship between pointers and arrays in C is a major sticking point.

It claims that, given a declaration

    int array[] = { 45, 67, 89 };
the expressions "array", "&array", and "&array[0]" are all equivalent. They are not. "&array" and "&array[0]" both refer to the same memory location, but they're of different types.

In the next section:

"By the way, though sizeof(void) is illegal, void pointers are incremented or decremented by 1 byte."

Arithmetic on void pointers is a gcc-specific extension (also supported by some other compilers). It's a constraint violation in standard C.

I don't think this is the "best" article on pointers in C.

I usually recommend section 6 of the comp.lang.c FAQ, http://www.c-faq.com/



> "&array" and "&array[0]" both refer to the same memory location, but they're of different types.

Indeed. And isn't this what makes the classic "countof" macro possible? The macro returns the number of elements in an array, calculating this by dividing the size of the array by the size of the first element:

  #define countof( array )  ( sizeof(array) / sizeof((array)[0]) )


The C faq presents as supposedly common questions and answers without saying what a pointer is.

In contrast I really like the posts definition.

> A pointer is a memory address.

Not perfect but for concision and accuracy it cannot be beat.


Well, it's concise, but it leaves out something important. A C pointer has both a memory address and the type of whatever it believes it points to. The latter is what makes C-style pointer arithmetic possible, because if you know the type, you know its size too.


Right. That's why, when you have an array of structs, say each 8 bytes in size, and a pointer ptr to the start of it, each time you do:

  ptr++;
it increments the address stored in ptr, not by 1, but by 8 (bytes) - so as to now point to the next struct in the array. Same if you do:

  ptr--;
except in that case it decrements the address by 8.

And if you did:

  ptr += 2;
it would increment the address in ptr to point to 16 bytes further ahead in memory than it was earlier, for the same reason. ptr will now point to the struct which is two items further ahead in the array. So you can access that struct with the expression:

  *ptr


[deleted]


C pointers being typed is absolutely central.

Firstly, because a pointer is typed, when we dereference it, the memory location is accessed properly as the type. We don't have to put anything in the expression to say "please access the memory as 'struct foo', or a 'double'".

Secondly, arithmetic on pointers for array-like manipulation uses correct displacements for the size of the type.

Thirdly, we get error checking: there is a good amount of resistance in the language against mixing up types. If we convert one type of pointer to another without a cast, we get a diagnostic.

All these aspects are a key aspect of what separates C from assembly language.


> C pointers being typed is absolutely central.

No it isn't. The rest of your post shows why.

> Firstly, because a pointer is typed, when we dereference it

Nonsensical and circular.

> Secondly, arithmetic on pointers...

Nonsensical and circular.

> Thirdly, we get error checking: there is a good amount of resistance in the language against mixing up types. If we convert one type of pointer...

Circular.

Every single one of your points about why this is an important property of pointers presupposes that the reader knows what a pointer is and how it works. It's a fundamental truism that you can't introduce, explain, and define a term using the term itself.

The OP's introduction does not suffer from this problem, which is why it is superior to your proposal.

(Edit: You all can downvote me all you want, but you're still objectively wrong.)


Please don't post in the flamewar style to HN. It leads to increasing nastiness, as happened below, and you did quite a bit to fuel that.

If you'd please review https://news.ycombinator.com/newsguidelines.html and take the spirit of this site to heart, we'd be grateful. We don't want threads to go up in flames.


I'm sorry, but I disagree. The very first sentence of the linked article is being criticized unfairly. People are claiming that the very first sentence is incomplete because it should explain pointer arithmetic and typing.

Explaining what a pointer is by using the term "pointer" and explaining how to use one is a circular definition. A student who has never heard the term "pointer" before is going to be completely lost if the instructor jumps straight to "Pointer arithmetic works by increments of the size of the type of object to which the pointer points." If that is the first statement someone is exposed to, it will not make sense to them. I understand that someone may not like their proposal being called circular, but I don't think it's unfair or bad behavior to point it out.

The linked-to article is well done. There are some minor errors, as have been pointed out elsewhere. However, I think the criticism in this thread of conversation is deeply negative in a hostile, nasty way.


Dan's request wasn't about the technical merit of your comments, it was about the style:

> Nonsensical and circular.

> Nonsensical and circular.

> Circular.

> You all can downvote me all you want, but you're still objectively wrong.

If you feel that something is being criticized unfairly, you will win more people over to your point of view with civil and respectful conversation instead of flamebait.

We can have different opinions about something and still have an enjoyable conversation and learn from each other, as long as we interpret each other charitably and have a sense of humor about it.

Fair enough?


I am all on board with being civil and respectful. It's the matter of application where I seem not to be of the same mind as some others. I don't want to belabor the point, but I don't agree with the way this played out.

If you'd like to discuss further, my email is my username at GMail dot com. Otherwise, I'm going to let this lie.


> Every single one of your points ... presupposes that the reader knows what a pointer is and how it works.

Pardon my mistake; I made these points under the belief that I'm in a debate about how to teach pointers to a beginner audience, not that I'm addressing that audience itself.


Come on, please don't be a jerk on HN.


I think this was misunderstood due to not being phrased right; I do not believe, let alone wish to insinuate, that torstenvl is a newbie who doesn't understand pointers. I mean, that doesn't even make sense. Though flagged, it was also upvoted to +2 at one point.


I'm sorry if I misread you. The comment sounded like a putdown to me, but it's easy to misread intent, especially when reading quickly, as moderation entails. If I read it the opposite way, I think I can see what you mean—but then it's a subtle enough point that the nuance was likely lost on more readers than just me.


Distinction without a difference. If you can't explain to your students why the words out of your mouth are useful, you've already lost them.

The fact of the matter is that you're getting hung up on implementation details that do nothing to explain the concept.

Pointer arithmetic, how pointers interact with the type system... those are important, but they're completely incidental. There's no fundamental reason C had to implement pointers that way. They're factoids about how pointers work in C, practical mechanics. They tell you nothing about what pointers are in essence: a memory address.

And I don't know if you've worked with novice programmers lately, but it seems pretty obvious to me - based on how so many people get their heads discombobulated about pointers - that that conceptual clarity is needed. The details you're listing don't need to be in the first sentence.


That C pointers have a type which is used for checking, semantics of memory access and arithmetic falls into a category that I like to call requirements, whereas I use the word implementation detail for something like how many bits are in a pointer, and do pointers to bytes have the same representation as pointers to wider types. (These are also requirements, but ones an implementation can vary.)

I accept your terminology, though. Let the key features of C be called "implementation details". Then, if the goal is to teach C, it is "implementation details" we must teach.

(In any case, the students are probably eager to learn implementation details in the regular sense also, otherwise they would have been satisfied with their Python or Javascript class.)

If what you really mean is that we should teach C by instead teaching the architecture of a machine, whereby C is treated as just a notation for manipulating that machine, and concepts like the type system are considered annoying/distracting details in that notation, then I strongly disagree. Even if it is easier to teach that way, and the students eagerly absorb and regurgitate the simplified misconceptions, they are ill served.


[deleted]


Please stop now.


Problem is, the author isn't using that definition as a rung on Witgennstein's ladder. He believes it himself. That's why he's oblivious to the differences between array, &array and &array[0]. The expressions produce the same memory address and since that is what a pointer is by definition, they are equivalent.


> He believes it himself

Not necessarily a strong belief, just an amount of ignorance perhaps.


> "&array" and "&array[0]" both refer to the same memory location, but they're of different types.

"&array[0]" also does a dereference, which may cause undefined behaviour if array is null or invalid. You can get fun stuff like the optimizer omitting null checks later on.


Interesting. In C, “&array[0]“ is the same thing as “(array + 0)”, but in my copy of the C11 draft standard, I can't find anything about adding zero to a null pointer.


"&array[0]" is equivalent to "&(array[0])" - [] has greater precedence than &. Desugared, this is "&(*(array + 0))" - add 0, dereference, then take the address.


Yes, you're right about precedence.


Is there a valid use case for doing pointer arithmetic on a void pointer? It just seems so disgusting on the surface. Did someone just really not want to bother casting to uint8_t*?

As for the array types, I think maybe the article is really trying to say that they'll all compile down to the same thing in the end (probably...compilers can be weird sometimes).


> Is there a valid use case for doing pointer arithmetic on a void pointer? It just seems so disgusting on the surface. Did someone just really not want to bother casting to uint8_t * ?

As a side note, there was a debate whether casting to `uint8_t * ` is a strict-aliasing issue (As `uint8_t` and `char` are not guarenteed to be the same type, and so the compiler isn't required to treat them as such).

That said, you're correct that you can always cast to `char * ` in this case as `char * ` is allowed to alias anything. I do agree pointer arithematic on `void * ` a bit messy, I think the intention was just as a convenience thing, but it can definitely be abused.

> As for the array types, I think maybe the article is really trying to say that they'll all compile down to the same thing in the end (probably...compilers can be weird sometimes).

I donno, I've read this page before and think the author genuinely doesn't know about pointers to arrays - if they do they've done a good job of pretending they don't exist. They are not mentioned anywhere on the page (Besides the part that says they're are all the same), and near the top the author talks about parentheses in variable declarations and says:

> This is not useful for anything, except to declare function pointers (described later).

Which anybody who has used a pointer to an array will know is not true, as you have to use parentheses to differentiate between a pointer to an array from an array of pointers.


> `uint8_t` and `char` are not guarenteed to be the same type

Remember that `char`, `signed char` and `unsigned char` are the different types, even though `char` takes the same range of values as either `signed char` or `unsigned char`.

The typedef `uint8_t` is usually set to `unsigned char`, not `char`, even on systems where `char` is unsigned. Partly this reflects the fact that `char` is usually used to represent actual characters, while the other two types are usually used to represent integers that take the same amount of memory as `char`. The standard technically does not allow `uint8_t` to be `char` [1], although this requires an extremely pedantic reading.

Anyway, if you replace `char` with `unsigned char` in your comment then it's correct. I believe all current major implementations typedef `uint8_t` to `unsigned char`, but that's not guaranteed and even old implementations of GCC had a different type. `unsigned char` satisfies the same relaxed aliasing rule as `char` [2] but `uint8_t` may not.

[1] https://stackoverflow.com/a/16002781

[2] https://stackoverflow.com/a/40575162


> I believe all current major implementations typedef `uint8_t` to `unsigned char`

That can't be right, because CHAR_BIT is not always 8.


Practically speaking, for most people these days it is. POSIX specifies 8 bits and windows uses it. If you're working with a larger bit char, you're most likely well aware of it (offhand, some TI embedded chips are the only ones I know of, and even theyre rare!)


It depends on what you count as a "major implementation".


Casting to uint8_t* is not portable either, so it's not particularly better than using compiler extensions that let you increment void*.


"char" is portable and by definition sizeof(char) == 1 and on top of that char pointers can alias other types.

If I could time travel and influence the design of C the first things I'd do is change switch to break by default. The 2nd thing I'd do is rename "char" into "byte" since that's effectively what it is (and it's seldom a character these days since we often use UTF-8 or other multi-byte encodings).


> The 2nd thing I'd do is rename "char" into "byte" since that's effectively what it is

This isn't always true. See machines where CHAR_BIT > 8.


A byte is the smallest addressable memory unit, nothing more, nothing less. It's true that on most modern machines it's always equal to 8 bits (and it's mandated by POSIX AFAIK) but that's orthogonal.

In French when talking about storage capacity we use "octet" instead of "byte", I always thought that made more literal sense (if you have a 1megabyte memory on a system where CHAR_BITS is 16, do you have 8 or 16 megabits?).


There are bit-addressable CPUs and DSPs. Examples are many microcontrollers (part of the memory accessible in bit-addressable windows), and famous TMS34010 (https://en.wikipedia.org/wiki/TMS34010).

4-bit CPUs (like the one in your toothbrush or thermometer) can also of course address 4-bit "words", "bytes" or nibbles.

So 8 bits might not be the smallest addressable memory unit.


I always thought that 1 char was exactly 1 byte, but that 1 byte was not necessarily 8 bits?


I think from a C standards point of view, that's true; I was using the general convention of 1 byte=8 bits, but char being one or more of these bytes. Hence CHAR_BIT is the number of bits in a char, and sizeof gives the number of chars that would fit in the specified type.


Dude, I've just been asked to teach c pointers and this was super helpful. Got a good c++ FAQ? :)


Marshall Cline made a good one, here's a mirror, the original seems down: http://www.dietmar-kuehl.de/mirror/c++-faq/references.html


Marshall Cline's C++ FAQ is really, really good, IMO. I had read it some years ago, I think both the online version and the book, even though I've not really worked on C++ in projects. It goes into good depth and detail, not only on C++ language features and points, but also a lot into OOP/OOAD topics. Things like why you should (sometimes) push code from the client to the server - push in the sense of shift, not in the sense of mobile code, how old code can call new code - that didn't exist when the old code was written - don't remember the full details of that one right now, it may be via virtual functions and inheritance), and many more such things, a lot of them very useful. And all written in a fun, engaging style.


Marshall Cline’s FAQ been absorbed by the “official” C++ FAQ: https://isocpp.org/faq




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: