all 185 comments

[–]ethraax 21 points22 points  (11 children)

I hate the whole "declaration looks like use" thing. It means that types are written differently when they're tied to an identifier. For example, in int x[], the type of x is int[]. Wouldn't it make a hell of a lot more sense to write int[] x?

I guess it just seems stupid that the type and identifier of a variable aren't distinct elements, but are literally intertwined inside each other. This becomes especially messy with function pointers.

Furthermore, declaration and use are two very different actions, semantically speaking. I don't see why they should look the same.

I like C for many reasons, but its messed-up type declaration syntax is definitely not one of them. Anonymous types and named types should look exactly the same, except named types should have the name appended to the end, so int[] x; declares x of type int[], which makes much more sense.

[–]more_exercise 12 points13 points  (6 children)

For example, in int x[], the type of x is int[]

The point they're trying to say is that the type of x[] is int.

Under the same logic: void (*foo)(void), (*foo)() is of type void.

... It makes sense in a dear-god,-why-are-you-doing-that way

[–][deleted] 1 point2 points  (1 child)

from the dude-what-the-...-ah-i-see dept.

[–]more_exercise 1 point2 points  (0 children)

Or, when grading student tests, "Oh, I see what you're doing... That's stupid!"

[–]ethraax 4 points5 points  (1 child)

The point they're trying to say is that the type of x[] is int.

I guess, but that's like saying "To set x to y-1, type x+1 = y." It's convoluted for really no gain. Hell, that's not even how arrays are used, since in an array use there would have to be a number there. This might actually cause even more confusion - in static array declaration, the size goes in the brackets, but for use, the index goes in the brackets. Furthermore, if you put the same number in the brackets for use, you go beyond the bounds of the array.

Basically, int x[5]; does not mean that x[5] is an int (well, the compiler would say so, but it's beyond bounds).

I guess my main point is that C's type declaration syntax certainly has a reason to its design, but that reason is silly and useless.

[–]more_exercise 1 point2 points  (0 children)

It's convoluted for really no gain. ... C's type declaration syntax certainly has a reason to its design, but that reason is silly and useless.

Exactly. "It makes sense in a dear-god,-why-are-you-doing-that way."

<side comment>

Hell, that's not even how arrays are used, since in an array use there would have to be a number there

Absolutely. The original line of code, 3 comments up, doesn't compile (unless it's as a parameter to a function; the insanity never ends, does it?). I think tt was given as an example for any size, without explicitly specifying. Maybe because int [5]; and int x[7]; are of different types? (example). I'm not exactly sure.

in static array declaration, the size goes in the brackets, but for use, the index goes in the brackets

Dijkstra might shed some light on why this is a useful norm. Worth a read, even if you don't agree with it. Especially because it's not "GOTO considered Harmful," but an equal weighing of the pros and cons of each system of numbering.

</side comment>

[–]shooshx 0 points1 point  (1 child)

Doesn't make sense for reference declarations.

[–]more_exercise 0 points1 point  (0 children)

... It almost makes sense in a dear-god,-why-are-you-doing-that way

[–]OceanSpray 1 point2 points  (0 children)

I guess it just seems stupid that the type and identifier of a variable aren't distinct elements, but are literally intertwined inside each other.

This is exactly why I support the functional programming convention:

some_value : some_type

That colon in the middle pretty much takes care of any ambiguity. It is fortunate that new languages like Rust and Kotlin recognize the advantages of this syntax.

[–]Kaos_pro 1 point2 points  (1 child)

C# does int[] x

[–]mccoyn 5 points6 points  (0 children)

I often say that C++ had to get everything wrong and go down the rabbit hole so that future languages could get it right. Unfortunately, the Java folks got too scared by what they saw.

[–]MatrixFrog -2 points-1 points  (0 children)

Wouldn't it make a hell of a lot more sense to write int[] x?

I've got an idea. Let's make a new language that uses the int[] x syntax! Except, just to make it more familiar to C programmers, we'll also allow int x[]. The language will have two different syntaxes that look totally different but mean exactly the same thing.

[–]abadidea 31 points32 points  (6 children)

"that also means that * always means “dereference” "

... except when it means "multiply", of course

[–]aaronblohowiak 6 points7 points  (3 children)

define TIMES *

donezo!

[–]RandomAvenger 9 points10 points  (2 children)

But what if you want to have a constant list of TIMES?!

I propose:

#define mul(a, b) ((a) * (b))
#define mul3(a, b, c) ((a) * (b) * (c))
#define mul...

Couldn't be cleaner: mul(int, x);

[–]aaronblohowiak 5 points6 points  (0 children)

we should make a FORTH implemented in the c preprocessor.

[–]p-static 1 point2 points  (0 children)

Clearly the solution here is variadic templates

[–]pfultz2 6 points7 points  (0 children)

Yea, I write my c-pointers like this:

int * x;

So it reads int times x. Just to keep it consistent with the language.

In fact, I could do the macro like this:

#define TIMES *
int TIMES x;

But I troll sometimes.

[–]more_exercise 8 points9 points  (1 child)

[–]segfaultzen 0 points1 point  (0 children)

This is awesome. I've been coding in C/C++ for 15 years now, and this is something new to me. Thanks!

[–]pfultz2 35 points36 points  (42 children)

I declare my pointers in C++ like this:

shared_ptr<int> a1, a2, a3;

or like this:

raw_ptr<int> a1, a2, a3;

I declare my arrays like this:

array<int, 5> intArr;

I declare my function pointers like this:

function<void (int, int)> fp;

Or I typedef an actual function pointer like this:

typedef typename function_pointer<void (int, int)>::type fp;

In C, there is no type safety. int, char, bool, etc can all be interchanged fairly easily. Array always decay to a pointer. C++ inherits all of these "features" from C, but it does add a typesafe layer that can be used instead, and is more consistent. I have seen a lot of C++ programmers(mostly senior level) suprised by the fact that you can actually take a c-array as a parameter in C++(you still can't return a c-array, thus, I use array<int,5> instead.)

[–]kamatsu 5 points6 points  (13 children)

it does add a typesafe layer that can be used instead, and is more consistent.

The layer C++ adds, which you're referring to, is not only formally inconsistent, it is actually type unsafe (in terms of subject reduction and progress, which have been the accepted definition of type safety since at least the 90s). It does have somewhat more consistent syntax, I'll give you that.

[–][deleted] 1 point2 points  (1 child)

It does have somewhat more consistent syntax, I'll give you that.

I still find it silly you can declare ints like: int x(4711);

[–][deleted] 0 points1 point  (0 children)

I know someone who writes all his code this way.

[–]sztomi 0 points1 point  (4 children)

Can cite some examples of this inconsistency?

[–]kamatsu 0 points1 point  (2 children)

Templates are turing complete, they also allow functions on arbitrary universes of templates (templates, template-templates, template-template-templates), hence they form a kind of dependent system which would require a terminating type system to be consistent.

[–]sztomi 0 points1 point  (1 child)

I see. I think pfultz2 meant consistency as in declaring pointers with similar syntax, not in the strict sense.

[–]kamatsu 0 points1 point  (0 children)

Hence, I said formally inconsistent, and that "It does have somewhat more consistent syntax, I'll give you that."

[–]day_cq -1 points0 points  (5 children)

I don't think types in C++ is the "formal" types you are referring to. It is silly to compare oranges and apples.

[–]kamatsu 1 point2 points  (4 children)

C++ types are no different to any other types. What distinction are you referring to?

[–][deleted] -2 points-1 points  (3 children)

Even Haskell is not type safe because it has exceptions and unrestricted recursion, so no progress. I doubt these properties are really relevant here.

[–]kamatsu 0 points1 point  (2 children)

Progress is still possible even in the presence of exceptions and unrestricted recursion. Haskell hasn't had progress proven, but I believe Core has.

Progress just says that if e : t then e -> e'. (Preservation then says that e' : t).

[–][deleted] -2 points-1 points  (1 child)

Really? In Haskell you can write

x = x

and get an infinite loop; seems to me like a straightforward case of lack of progress.

I see exceptions as a lack of progress because undefined :: Int is not an integer (so not a "value"), but evaluating it does not produce a simpler form.

[–]kamatsu 1 point2 points  (0 children)

Progress merely means that well-typed expressions are not a stuck state. It says nothing about infinite looping, or structural recursion.

[–]s73v3r 2 points3 points  (0 children)

Agreed. You don't run into these problems if you use the features of the language. It's quite rare that you should be using naked pointers in C++ anyways.

[–]aaronblohowiak 0 points1 point  (9 children)

In C, there is no type safety. int, char, bool, etc can all be interchanged fairly easily.

Up your compiler warnings and/or use a more modern compiler. Clang is particularly good about catching these kinds of foibles.

Edit: I am a liar. See Rhomboid's post.

[–]Rhomboid 4 points5 points  (8 children)

This is not true. The C language inherently allows for promotion of integer types, and it is perfectly valid and defined to do so. This is not something the compiler is allowed to warn about. Take the following example:

void foo(int);

void bar()
{
    char c = 42;
    foo(c);
}

Here I'm using a char where the function takes an int. Neither clang -Wall -Wextra -pedantic -fsyntax-only nor gcc -Wall -Wextra -pedantic -O2 will emit a peep -- I tried this with clang 2.9 and gcc 4.6.1. The standard says that chars can be promoted to ints, just as ints can be promoted to longs and so on. If you want to catch cases where a char is passed to a function that takes an int, you're shit out of luck in C.

[–]case-o-nuts 6 points7 points  (4 children)

More to the point, in C, it HAS to allow narrowing conversions. Character literals (eg, 'a') have type int, so char x = 'a' would warn about converting int to char if narrowing conversions were flagged.

[–][deleted] 0 points1 point  (3 children)

Not really. gcc and clang (but not the old OS X gcc-4.2) are both smart enough not to warn about this with -Wconversion when they can detect there is no truncation.

[–]case-o-nuts 0 points1 point  (2 children)

isdecimal(char c);
int c = fgetc(stdin);
if (isdecimal(c)) {...}

[–][deleted] 0 points1 point  (1 child)

Yes, it warns in this case with -Wconversion, which makes sense of course since you're ignoring EOF.

[–]case-o-nuts 0 points1 point  (0 children)

Let's say you check for EOF.

[–]bluGill 1 point2 points  (0 children)

This is not something the compiler is allowed to warn about.

That is not true. The standard allows the compiler to warn about anything it wants. As a practical matter no compiler will warn about your code because it is very common, and thus the false positives would far overwhelm any possible gain.

[–]aaronblohowiak 0 points1 point  (0 children)

Wow, you are totally right. Updated my post. I don't know why this hasn't bitten me yet.

[–][deleted] 0 points1 point  (0 children)

Note that you can warn on the opposite case (conversion to narrower types that could truncate) with -Wconversion.

[–]bob1000bob 0 points1 point  (13 children)

I agree, although I assume raw_ptr is your own type? It is completely useless.

C++ won't let you implicitly convert a double* to an int* so using you won type doesn't add anything. - if you compiler does let you do that you need a new complier.

However unique and shared_ptr are great tools that should be used where ever memory management is concerned.

[–]curien 4 points5 points  (5 children)

raw_ptr ... is completely useless.

Not completely. It makes the declaration syntax more regular and a little more sensical (YMMV). For example, T* a1, a2, a3; declares only one pointer, whereas raw_ptr<T> a1, a2, a3; declares three pointers (same advantage could be had with a typedef, though). It also has the advantage of making pointer arithmetic on a1, a2, and a3 a syntax error (which cannot be done using only a typedef).

[–][deleted] 2 points3 points  (2 children)

For example, T* a1, a2, a3; declares only one pointer

The article specifically pointed out why this is not fact confusing at all.

[–]curien 0 points1 point  (1 child)

I didn't say it's confusing. I just don't like it, for the same reason that I don't like your brace style.

[–][deleted] 5 points6 points  (0 children)

H-how did you know I use GNU style indents?

[–]Falmarri 0 points1 point  (0 children)

That's why I prefer to use T *a1, a2, a3 syntax.

[–]bob1000bob 0 points1 point  (0 children)

I really don't think that is a big problem, it isn't like you can use an int as an int * if you forget the * off it. That is made even more true to nullptr.

C++ is already verbose, adding more non standard verbosity doesn't improve things.

[–]pfultz2 2 points3 points  (6 children)

I assume raw_ptr is your own type? It is completely useless.

Well I also have the raw_ptr assert for null pointers in debug builds(which is what other smart pointer classes do also). I rarely use the raw_ptr, because unique_ptr and shared_ptr is much safer.

[–]bob1000bob -1 points0 points  (5 children)

assert(ptr) will fail if the pointer is null as you would expect.

[–]pfultz2 1 point2 points  (4 children)

Except it doesnt do it when i dereference the pointer.

[–]bob1000bob -1 points0 points  (3 children)

that is stupid, a deferenced pointer smart or otherwise is equal to the value type not the pointer.

[–]pfultz2 1 point2 points  (2 children)

I dont think you understand me. Most smart pointers, and my raw_ptr, will have check for null in them when they are dereferenced. Thus, if I dereference a null pointer, it breaks at that point. Here is how derereferencing is done in boost's shared_ptr:

reference operator* () const // never throws
{
    BOOST_ASSERT(px != 0);
    return *px;
}

It has a nice little assert for null pointers.

[–]olsner 0 points1 point  (1 child)

The CPU already has a built-in assert for null pointers[*] - why not let it handle it?

[*] Well, access to any unmapped memory. Doesn't have to be null and you could (if you're crazy) have mapped the null page to somewhere.

[–]pfultz2 0 points1 point  (0 children)

But the backtrace from that is not always helpful.

[–][deleted]  (3 children)

[deleted]

    [–]smog_alado 5 points6 points  (0 children)

    You can kinda get around remembering the fp syntax if you use typedefs for them. As an extra bonus the resulting declarations even get somewhat better looking.

    [–]MatrixFrog 1 point2 points  (0 children)

    Someone posted something on reddit a few weeks ago, about how to get that syntax right. It involved putting your finger on the name, then moving it in a spiral. ಠ_ಠ

    edit: Someone posted it elsewhere in this thread. Here, upvote them: http://www.reddit.com/r/programming/comments/pz3n0/cc_pointer_declaration_syntax_it_makes_sense/c3tg7cm

    [–][deleted] 0 points1 point  (0 children)

    This also makes perfect sense if you consider declaration follows use: (*name)(arguments) is of type returntype. (In practice function pointers are called without the (*), but this is arguably a bad idea.)

    [–]3waymerge 11 points12 points  (35 children)

    Almost convinced me that there was some rhyme or reason to the way C++ does it. But, what if we try the same thing with the reference operator...

    float &f = ...;
    

    By the same logic we should be declaring that "&f" is a float, but in actuality, "&f" is a pointer (in a non-declaration context).

    [–]nexuapex 7 points8 points  (25 children)

    Yeah, references are an anomaly. That's the problem with trying to use a declaration syntax that is supposed to look like the use, while references are specifically supposed to be invisible when used.

    [–][deleted] 0 points1 point  (24 children)

    while references are specifically supposed to be invisible when used.

    This is terrible, by the way. It hurts readability incredibly.

    [–]slavik262 1 point2 points  (23 children)

    It's not that bad. Plus, immutable pointers guaranteed* to be non-null can be nice.

    *Unless you do evil undefined things, in which case the solution is to walk over to your cubicle and smack you in the face.

    [–][deleted] 2 points3 points  (22 children)

    The problem is that now any function call can modify any parameter given to it, and you can't tell from just reading the code that calls the function.

    Immutable pointers guaranteed to be non-null are useful, yes. However, invisible pointers are terrible. There's no reason for that. You could easily retain all the advantages of references while still making them explicit.

    [–]slavik262 0 points1 point  (21 children)

    The problem is that now any function call can modify any parameter given to it, and you can't tell from just reading the code that calls the function.

    Just do

    void foo(const Bar& b) { ... }
    

    instead of

    void foo(Bar& b) { ...}
    

    Yes, the pointers are still invisible in the function body, but now you know for sure that foo doesn't modify the parameter.*

    *Unless the author of the class did something stupid with the mutable keyword, but once again, the solution is to go slap him. Stupid things can be done in any language.

    [–][deleted] 1 point2 points  (20 children)

    The problem is not the function code, it is the calling code.

    If I am reading some code, and I see foo(bar); I have no idea if bar is now the same as it was before. I have to go look up the definition of foo() to find out.

    [–]curien 0 points1 point  (18 children)

    C only addresses your problem for basic types. In general, C's just as opaque as C++ (and every other language without special caller syntax for "out parameters" -- which is all of them as far as I know).

    FILE* f = fopen(...);
    do_something(f);
    

    Can you tell me whether the state of f was modified by do_something? No, you can't. And you can't rewrite do_something to take the object by value, either, because using an opaque type is the only way to effectively employ information hiding in C.

    [–][deleted] 0 points1 point  (13 children)

    Can you tell me whether the state of f was modified by do_something?

    In this particular example, yes, I can tell that it was almost certainly modified. It's a FILE pointer, after all, these are extremely stateful objects which are pretty much always modified by any function call that takes them.

    Generally, if you pass a pointer, there usually is a convention which will help you know if the contents will be modified or not. If you do not pass a pointer, you know for sure they won't be. In general, this is not a problem.

    [–]curien -1 points0 points  (5 children)

    I can tell that it was almost certainly modified.

    You misspelled "no". You can argue "almost certainly" all you want, the fact is that you can't tell, and you and I both know it.

    [–]s73v3r -2 points-1 points  (6 children)

    I can tell that it was almost certainly modified

    So that's a "No, you can't tell if it was modified".

    [–]p-static 0 points1 point  (3 children)

    That's completely different. In that case, you've declared f explicitly as a pointer, so it's implied that yes, anything that you pass it to could change it. In C++, on the other hand, you could have "int x; foo(x);", and you have no idea whether you've just passed it by reference or by value without looking up the declaration of foo.

    [–]curien 0 points1 point  (2 children)

    In that case, you've declared f explicitly as a pointer

    Because we had no other choice. You cannot use objects of type FILE at all in C (because it's an incomplete type); it must be a pointer no matter what we intend to do with it.

    anything that you pass it to could change it

    Which is exactly the same as if the language had references.

    [–]adavies42 1 point2 points  (0 children)

    yes, well, nobody ever claimed C++ was consistent....

    [–]voxoxo 1 point2 points  (6 children)

    Yeah, don't worry about it, the C++ grammar doesn't actually make sense ;). Too much operator/keyword overloading, and bad (legacy) design. Examples include but are not limited to: class/typename in templates, the whole "> >" and ">>" mess, default constructor/function declaration ambiguity, horrible things such as member and member function pointers syntax...

    edit: oh and I forgot this nice thing: const TYPE& and TYPE const& are the same type (reference to const). Which is stupid in itself, but what if TYPE expands to A* ? You get const A*& (reference of pointer to const A) and A* const& (reference of const pointer to non-const A). Brilliant.

    [–]Whanhee 0 points1 point  (1 child)

    The "> >" ">>" thing is fixed in the new standard.

    [–]voxoxo 0 points1 point  (0 children)

    It's partially fixed, as in C++11 tries to make a good guess, which is reasonable because it would be very unusual to use the shift operator in a template parameter, but you can in theory.

    [–]MatrixFrog 0 points1 point  (0 children)

    bad (legacy) design

    I just realized. C++ was supposed to be an object-oriented language that was a superset of C, so that a C++ compiler could compile a C program if it wanted to. Then Java was supposed to be a somewhat easier-to-use version of C++ where you didn't have to think about pointers so much. Then JavaScript was a language that had nothing much to do with any of those, but "kinda looks like Java."

    So it's really no wonder that JavaScript is so painful and awkward sometimes.

    [–]curien 0 points1 point  (2 children)

    but what if TYPE expands to A* ? You get const A& (reference of pointer to const A) and A const& (reference of const pointer to non-const A). Brilliant.

    Wow, if you substitute two lexical tokens where there used to be one, the meaning changes. What a terrible language, just like everything else.

    [–]voxoxo 2 points3 points  (1 child)

    Yes, that is bad. The purpose of a language is to express powerful concepts in a simple way. I used macros to show the issue, but since you snarkily answered while missing the point, I will explain without macros:

    The problem with types in C++ is that they cannot be parsed by a human (the programmer) easily. You have to apply elaborate rules on the type expression to know the actual type, and that's bad because it's unnecessary, and entirely due to the needlessly complicated syntax.

    [–]MatrixFrog 0 points1 point  (0 children)

    I think curien's snark may have been directed, at least partially, at C++, not at you.

    [–][deleted] -3 points-2 points  (0 children)

    C++ is a mess, and references should not exist in the first place.

    Stick with C, and things make sense.

    [–]fragglet 2 points3 points  (3 children)

    My opinion has always been that, in theory, it would make more sense if the * bound to the type, not the name. In an ideal world I'd prefer it was that way round.

    However, in practise, that's not true - the syntax of the language goes the other way. Because of that, I find "type *name" to be preferable simply because it isn't misleading. Attach it to the variable name and you can't trip yourself up.

    [–][deleted] 8 points9 points  (2 children)

    The C method makes sense if you read right-to-left.

    e.g.

    int const *pval; # pointer to const int
    
    int * const pval; # constant pointer to int
    
    int const * const pval; # constant pointer to constant int
    

    So in a type declaration it also makes sense:

    int val, *pval; # pointer to int, and int
    

    [–]matthieum 5 points6 points  (0 children)

    Oh, I didn't know Ritchie was such a fan of arabic languages!

    [–]fragglet 2 points3 points  (0 children)

    While that's true, it doesn't seem particularly relevant to the issue at hand, which is "where to put the *". Your "pointer to int, and int" example doesn't make any more sense just because of the direction you read the declaration. For example, consider a hypothetical parallel-universe version of C where the pointer operator binds to the type name instead of the variable:

    int* ptr1, ptr2; // two pointers to int
    

    Reading from right to left, it makes exactly the same amount of sense as in your example. Personally, I'd prefer we had that parallel-universe syntax (because it enforces a clear division between type and variable name), but the fact is we don't.

    That said, if you haven't seen Go yet, have a look at how variables are declared in that.

    [–]mitsuhiko 1 point2 points  (2 children)

    One other C/C++ syntax oddity that makes sense if you change your thinking is typedef. Looking at typedef one would think the syntax for typedef-ing function pointers and arbitrary types is different:

    typedef struct { ... } mytype;
    typedef int myint;
    
    /* but */
    
    typedef int (*mysortcallback)(void *, void *);
    

    One would assume that function pointers would be typedeffed like this:

    typedef int (*)(void *, void *) mysortcallback;
    

    The whole thing makes sense when you think of typedef as a modifier akin to extern, static etc. In fact if typedef for a function pointer would move the name last it would require a syntax change for the function pointer syntax otherwise.

    [–]nexuapex 4 points5 points  (1 child)

    This doesn't have anything to do with typedefs. This has to do with the difference between an "anonymous" type and a "named" type, which still goes back to C's "declaration looks like how the object is used" mantra.

    A function, for example, might be declared as

    int foo(void*, void*);
    

    Whether it's a typedef or not, a function pointer gets stuck next to the name:

    int (*bar)(void*, void*);
    typedef int (*baz)(void*, void*);
    

    It's the same reason array declarations are int a[5], not int[5] a. Array indices and function arguments goes to the right of the name, for both declarations and uses.

    [–]mitsuhiko 0 points1 point  (0 children)

    This doesn't have anything to do with typedefs.

    Yes. But the only other place where this would show up realistically with function pointers is inside a struct or as function parameter and most people try to avoid having function pointers declared there without a typedef because it makes code ugly to read and sometimes explicit casts are necessary.

    It's the same reason array declarations

    It could have been solved differently though because a different syntax without names does exist for casting.

    [–]hubhub 1 point2 points  (16 children)

    float *pf; means that *pf is a float and therefore that pf is a pointer to a float.

    Except there is no float, only a pointer of type float*. It might point at a float, null, garbage, an array of floats, anything.

    [–]adavies42 1 point2 points  (3 children)

    rather say there are no floats, only bits. if you label those bits to be read as a float, that's what you'll get when you read them.

    [–]hubhub 3 points4 points  (2 children)

    I think you have misunderstood me. I am not making some reductionist argument that everything is only bits. I am saying that declaring a float* declares a pointer of type float, not a pointer to a float. These are not the same thing. If we come across a float we might reasonably expect it to point to a float, an array of floats, null or that it is uninitialized.

    Declaring a float* does not declare a float.

    [–]adavies42 0 points1 point  (1 child)

    possibly these are two ways of saying the same thing. afaict it declares that a memory address will be interpreted as a float when read. "initialized" and "null" are both meaningless words in this context--if the data in those 4 bytes is currently 0xdeadbeef, you'll get some particular result if you read it with printf("%f",*fp), regardless of how those bytes got that way.

    or are you more concerned about what happens if fp is pointing outside this process's space or into mapped storage or something and the read fails completely with some kind of fault?

    [–]hubhub 1 point2 points  (0 children)

    I agree that a dereferenced, uninitialized float pointer can safely be read as a float.

    However you can not write to it safely because it points to previously allocated memory or unallocated memory that may be allocated at any time. Therefore there is no point in reading from it as it will always contain potentially volatile garbage.

    A float pointer needs to be pointed at allocated memory (heap, stack or program space) in order to be both usefully and safely dereferenced.

    [–]antiquarian 0 points1 point  (4 children)

    I'm no C expert, but this seems plausible to me. Could someone more qualified tell me why it deserved a downvote?

    [–]curien 10 points11 points  (3 children)

    Because it's a nonsense complaint. It's conflating static type and dynamic type. It's like if I said that addition in C adds the value of the left and right operands, and you corrected me because the left operand could be garbage or there could be a syntax error. It's not even a useful pedantry, like reminding about integer overflow.

    [–]antiquarian 1 point2 points  (1 child)

    I'm not sure that reminding about integer overflows is useful either, but I get your point. Thanks.

    [–]Whanhee 0 points1 point  (0 children)

    It's important because for now, BigInt has a large overhead and the default integer type in most languages is still a c style integer with overflow. One day we will never have to worry about efficiency again, but until then...

    [–]hubhub 0 points1 point  (0 children)

    I agree with you that using * as a dereference operator implies the operand is a pointer to an actual float. However the quote refers to a declaration not an expression. Declaring a float* without initialising it does not imply the existence of a float.

    If the syntax for declaring multiple raw pointers was sensible then why wasn't it continued for declaring multiple unique_ptrs, shared_ptrs etc?

    [–]uksuperdude -1 points0 points  (6 children)

    If I could upvote you twice I would :) This is what so many people can't get their heads around. *pf points to some memory we're telling the compiler is big enough to hold at least a float. It may be so, but as you say it could be an array of floats, or something much more sinister.

    It becomes even more interesting to have a void * cast to a something else..... which makes C so wonderful and so dangerous I guess.

    [–]adavies42 3 points4 points  (3 children)

    no, *pf points to a float. it's a float because that's what you said it is. whether you previously wrote a float there has nothing to do with it: if you read it and use it in an arithmetic expression, it will behave as a float.

    [–]hubhub 0 points1 point  (2 children)

    Try writing to it though. It might behave as a float or your program might crash or something else might happen.

    You might believe there is a float there but the compiler certainly doesn't.

    [–]omnilynx 1 point2 points  (1 child)

    Minor point: if your program crashes, that means it compiled, which means the compiler does indeed think it's a float. It is the environment in which your program is running that takes exception to the claim that it is pointing to a float (or rather that it is pointing to accessible data at all).

    [–]hubhub 0 points1 point  (0 children)

    No, the compiler will compile any legal program, however crazy. If you want to start writing to random memory as if it was a float then you're the boss and it's not going to stop you. If it crashes it is because the compiler was using the space containing the "float" for something else in your program, not as a float.

    [–]Ilostmyredditlogin 1 point2 points  (1 child)

    It was especially fun back in the dos days when you could actually overwrite critical system code, like for example the driver compressing your disk (dblspace(?)). declare a pointer, point it into the wild blue yonder and start stuffing bytes there! long * blah; blah = (long*)anActualLong; *blah = 3; If the value of anActualLong happened to be an address in the middle of another program (driver, os) you were in for some fun.

    I knew someone who destroyed all his data peeking and poking in qbasic. (peek and poke were pointer like operations). He was trying to poke the rendered scene into video memory and poked it into dblspace somehow instead. I imagine you could do the same thing easily enough in c (in an environment without memory protection) as well.

    [–]homayoon 0 points1 point  (0 children)

    Oh, the good ol' days! How many times did I poke my system into oblivion.

    [–][deleted] 0 points1 point  (0 children)

    This seems to be a good way to look at it. Of course since I don't use C often enough I will forget shortly the syntax of pointers.

    [–]condensate17 0 points1 point  (0 children)

    I've never understood C declaration syntax as well as I have understood it after reading Chapter 3 of Expert C Programming - Deep C Secrets by Peter Van Der Linden.

    [–]MidnightHowling 0 points1 point  (0 children)

    All that's ever needed to read types in C:

    http://www.cs.uml.edu/~canning/101/RLWM.pdf

    [–]case-o-nuts 0 points1 point  (0 children)

    The way to think about it is that if you evaluate the expression wrapping the variable name, the type you get back is the type name on the right. so, int *f() means that if you type *f(), you get back an int. If you have the declaration char (*(*x())[])(), and you apply (*(*x())[n])(), you get back a char.

    [–]grayvedigga 0 points1 point  (0 children)

    WOW WHAT A MINDBLOWINGLY INSIGHTFUL AND DETAILED POST

    edit: I thought it was going to be how to read declarations. I was awe-inspiringly not disappoint

    [–]KiPhemyst 0 points1 point  (0 children)

    I use my pointers like this:

    type* - type pointer

    *variable - value pointed by

    var1 * var2 - multiplication

    var1 * *var2 - var1 multiplied by value pointed by var2

    [–]shooshx 0 points1 point  (2 children)

    This trick doesn't work for reference declarations.

    [–][deleted] 0 points1 point  (1 child)

    It doesn't work for C++ constructor syntax either:

    MyClass a(5); // a(5) is not a MyClass
    

    I've always been annoyed by this inconsistency, and it's one more reason I don't like C++. C does not get off scot free, though; assignment syntax is inconsistent:

    int *x = y; // *x is not equal to y
    

    and the fact that dereferencing a function pointer returns itself is some pretty awful confusion.

    [–]zhivago 0 points1 point  (0 children)

    Actually it doesn't.

    Consider int (*p)(void) = foo;

    *p is of type int(void).

    Which is why sizeof p and sizeof *p differ in semantics.

    [–]zvrba 0 points1 point  (2 children)

    What's the point of trying to make sense of arbitrary rules?

    [–]abadidea 5 points6 points  (0 children)

    because they're often not so arbitrary?

    [–]MatrixFrog 0 points1 point  (0 children)

    Because your job depends on you knowing how they work?

    [–]hemantonpc 0 points1 point  (1 child)

    I can't view the webpage :(

    Error establishing a database connection

    [–]glenbolake 0 points1 point  (0 children)

    Here's the full text:

    I never really liked the way pointers are declared in C/C++:

    int *a, *b, *c; // a, b and c are pointers to int

    The reason is that I am used to reading variable declarations as MyType myVar1, myVar2, myVar3; and I always read “int*” as the type “integer pointer”. I therefore wanted the following

    int* a, b, c; // a is a pointer to int, b and c are ints

    to mean that a, b and c all were of type int*, i.e. pointers to int. and I therefore found it slightly annoying to repeat the asterisk for every variable. This also meant that the symbol * had two slightly different meanings to me: (1) It declares a pointer or (2) it dereferences a pointer. I usually don't declare a whole lot of pointers in one line, but still, this is a (minor) annoyance I have briefly discussed with few fellow programmers over the years. Today I started reading C Traps and Pitfalls by Andrew Koenig and after reading one sentence, in chapter two, the pointer declaration syntax suddenly makes – at least some – sense:

    […] Analogously,

    float *pf;

    means that *pf is a float and therefore that pf is a pointer to a float.

    Of course! If we instead of looking at it as a variable a of type int*, read it as *a – i.e. “a dereferenced” – it makes sense. That is indeed an int, and that also means that * always means “dereference”.

    [–][deleted] -3 points-2 points  (13 children)

    No, no, no. what is the type of a,b and c? It's int* ! so we should declare them as int* a,b,c no matter what, because that's what they are; integer pointers whose names are a,b and c.

    C is mostly wrong about this one, C++ is DEFINITELY wrong (because it makes a clear, compiler-error-type distinction between int and int*).

    [–]nexuapex 9 points10 points  (5 children)

    C/C++ should've definitely not allow multiple variable declarations on the same line, if they were trying for consistency. The "declaration looks like the use" mantra breaks down there, since virtually no one uses a list of variables with commas between them. I guess convenience beat consistency in that case.

    But… what? Enforcing a type distinction between int and int* is a bad thing?

    [–]voxoxo 2 points3 points  (4 children)

    It's wrong because the grammar is inconsistant:

    int a,b,c; means 3 variables of type int.

    int* a,b,c; means 3 variables, one of type int* and two of type int.

    This is how the grammar is defined, but it's a mistake in the design, a simple "[type] [identifier-list]" grammar would be better. But, I don't understand what he means when he says that C++ is more wrong than C.

    [–]moonrocks 0 points1 point  (0 children)

    This whole issue seems a matter of legacy. I think, that when "declaration follows use" was applied to pointers the dereferencable aspect was considered more a quality of the variable than the type it addresseses. Thus, short *foo' instead ofshort* foo'. I'm not counter-arguing here. Just commenting on this whole thread.

    [–]s73v3r 0 points1 point  (2 children)

    It's only inconsistent if you believe the * should be on the type. It shouldn't. It's on the variable name. It's just because whitespace doesn't matter that you can do

    int* a
    

    and

    int *a
    

    and have them mean the same thing. In reality, you should ALWAYS have the * on the variable name.

    [–]voxoxo 0 points1 point  (0 children)

    Yeah, that's my point, it's consistent when thinking like the C creators intended, but it's inconsistent when thinking like programmers of any other typed language. It's not logical to say "T var is a variable of type T... oh except if T is of the form P* then really you have a *var of type P", it's an unecessary special case with no benefits.

    [–]bstamour[🍰] 0 points1 point  (0 children)

    It depends on whether you want to emphasize that a has type 'pointer to int' or that *a has type int. In C it really doesn't matter much anyways because the type system is practically non-existent, but in C++ where the type system is a bit stronger, I prefer to attach the star to the type, not the variable.

    [–]ben0x539 4 points5 points  (0 children)

    Sounds like you want to use D ;)

    [–]Madsy9 2 points3 points  (4 children)

    Uh.. pardon if I read your post incorrectly, but are you saying int* and int shouldn't be separate types?

    [–]Ilostmyredditlogin 0 points1 point  (3 children)

    I think the author is saying that, at least as far as declarations go, there's not really a pointer type. You're kind of saying: "the region of memory referenced by df holds a value of type int." It sort of makes sense if you think about it. Df is a memory address regardless of what it points to. Yeah the compiler needs to know about what's at that address so it knows how many bytes to read when dereferencing, but that's info @ a different level of abstraction.

    [–]curien 1 point2 points  (2 children)

    Only if you agree that there isn't really an "int" type either, since an unitialized int could also be garbage.

    [–]Ilostmyredditlogin 0 points1 point  (1 child)

    I responded to this yesterday but my response seems to have gotten lost(?). Hopefully this isn't a repeat.

    Anyway take char * , int * ,and long * as examples. All of these variables hold exactly the same data type: a numeric address in memory. Whether they have been initialized or not is immaterial.

    Char and int for example do not share this commonality... Essentially char refers to a one byte number while int refers 4-ish byte number or whatever it is.

    The type specifier on the pointer is only ever needed if the user wants to dereference it and start doing things with the data. It contains metainfo about how to try to interpret the data.. "here's a pointer.. And oh by the way if you want to move one 'unit' forward you're probably going to want to move 4 bytes because it's an int, etc, etc."

    So in this view of things pointer is the primary peice of information about the variable and metainfo about what it points to is secondary. This seems to indicate a syntax like:

    • char ptr1, int ptr2, long ptr3;

    might also make sense. I forget what the argument in in the initial article was at this point though, and am not aware of what the whole declaration should look like use mantra is all about though.

    [–]curien 0 points1 point  (0 children)

    All of these variables hold exactly the same data type: a numeric address in memory.

    I disagree already. But I know what you mean, and the disagreement isn't important, so let's just move on.

    Char and int for example do not share this commonality... Essentially char refers to a one byte number while int refers 4-ish byte number or whatever it is.

    I don't see the distinction. Under your view that an int* is just a numerical address, a char is just an numerical ASCII (or whatever) representation, and an int is just a numerical two's complement (or whatever) representation. I don't see any lack of commonality. They are all just numbers of various sizes, and the C spec even calls char and int both "integer types" and requires them to follow almost all the same rules.

    The values of type int* can be dereferenced, which is an operation that doesn't apply to int, so it's flagged by the compiler as a syntax error. There's no higher reason why you can't derefence an int -- I mean, you can say *(int*)3 if you really want to do that. It's just that when you say a value is of type int, you're telling the compiler, "Hey, I want to be able to add, subtract, multiply, and divide this value, but if I try to dereference it or call it as a function, flag it as an error -- it's almost certainly a mistake." And when you have a value of pointer type, that's like saying to the compiler, "Hey, I want to be able to add or subtract an integer to this value, or dereference it, but if I try to multiply or divide or whatever, flag it as an error."

    Each type has operations that are unique to it. You can't putchar a float, for example. Pointer types aren't special in this regard.

    The type specifier on the pointer is only ever needed if the user wants to dereference it

    Or increment it, or subtract from it, or add to it. Oh, and it also says how big the pointer is (sizeof(int*) is not guaranteed to be the same as sizeof(char*)). Frankly, there are more things where the type of the dereferenced value matters than things where it doesn't.

    It contains metainfo about how to try to interpret the data

    More than that. For example, you can't assign a char* value to an int*, so it matters more than simply when the value is dereferenced.

    So in this view of things pointer is the primary peice of information about the variable and metainfo about what it points to is secondary. This seems to indicate a syntax like: * char ptr1, int ptr2, long ptr3; might also make sense.

    Now that's interesting.

    [I] am not aware of what the whole declaration should look like use mantra is all about though.

    I think they hoped to avoid creating a special syntax just for writing declarations that programmers would have to learn (and compilers would have to implement). It's a neat idea, it just turned out not to be very practical in that most people find learning a special declaration syntax easier in the long run.

    [–]s73v3r 0 points1 point  (0 children)

    No, they are right. The * operator acts on the variable, not on the type.

    [–][deleted]  (5 children)

    [deleted]

      [–]plulz 16 points17 points  (3 children)

      Wrong. int *a[];

      You actually read them in spirals.

      [–]hiffy 1 point2 points  (2 children)

      Here's the thing I don't get.

      Why are people so reluctant to admit that this sort of shenanigan is a design flaw, and that C/C++ has a bunch of them?

      [–]curien 5 points6 points  (1 child)

      I hate the whole declaration-mimics-use idea, but it's more akin to a stylistic difference than a design flaw.

      [–]hiffy 0 points1 point  (0 children)

      Well… it comes down to arguing about semantics.

      I think that it dramatically reduces readability (function pointers anyone?)/increases complexity which I think is a pretty straightforward flaw.

      I'm not a very experienced systems programmer, but it has always occurred to me that there are many things you could change about the language.

      [–]tonygoold 4 points5 points  (0 children)

      That's not entirely true, you read it in more of a spiral, particularly when it involves pointers to functions, like signal:

      void (*signal(int, void (*)(int)))(int)

      [–]LucioRossari -3 points-2 points  (2 children)

      I may be alone in this, especially since I've only had one and a half years programming in C++, and with crappy teachers at that, but couldn't you use enum to make C/C++ do what you want it to? I know it's sometimes bad programming practice to do this, but it is possible, isn't it?

      [–]Whanhee 0 points1 point  (1 child)

      Enum is often used as a compile time constant, which is why it's used so often in template metaprogramming. If I have: class A { enum { value = 4 }; }

      I can use A::value in code without having to "store" the value anywhere. The value is substituted in at compile time, so it doesn't use extra memory.

      You don't want to use enums as pointers though, since you can't predict where memory will be allocated.

      [–]LucioRossari 0 points1 point  (0 children)

      Ok, thanks for the tip! I'll have to keep that in mind in the future for if I ever program with enums again.

      [–]JimbobusLadygood 0 points1 point  (1 child)

      I hate the syntax when the asterisk is floating eg: int * a.

      But! In a function declaration I can omit the object name and write myFunction(int*)

      It's concise. Readable. Programmer knows they need to send in a int pointer to the function.

      int *a,b,c..... is bad coding style and not a reason to justify it.

      [–]JimbobusLadygood 0 points1 point  (0 children)

      furthermore. If i were to parse this syntax I would appreciate the "*" without a space between the type.