char *combine(s, t)
char *s, *t;
{ char *combine(s, t)
char *s, *t;
{ char* combine(char* s, char* t) { ... }
which means combine takes in two pointers-to-char and returns a pointer-to-char.Opinions differ on whether the * should be next to the type or next to the identifier. I prefer putting it next to the type.
I mean, short of enabling declarations like:
char *a, *b;
But I have long since found the tradeoff to be worth the syntactic clarity. char* a, b;
Now a has type char * but b is just char. It’s probably not what the author meant and it’s definitely ambiguous even if it was intentional. Better to write: char b, *a;
Or, if you meant it this way: char *a, *b;
“Well, don’t declare multiple variables on the same line,” you respond. Sure, that’s good advice too. But in mixed, undisciplined, or just old code, it’s an easy trap to fall into. char* a, b;
apply the char* type to both? (That is, why didn't they design it that way?)I assume there was some reason originally, but it's made everything a bit more confusing ever since for a lot of people. :/
Edit: Apparently it's so declaration mirrors use. Not a good enough reason IMO. But plenty of languages have warts and bad choices that get brought forth. I'm a Perl dev, so I speak from experience (even if I think it's not nearly as bad as most people make out).
C does this very cute (read: horrifyingly unintuitive) thing where the type reads how it's used. So "char ⋆a" is written so because "⋆a" is a "char", i.e. pointer declarations mimic dereferencing, and similarly, function pointer declarations mimic function application, and array declarations mimic indexing into the array.
It helped than I've learned by K&R C book. Windows API and code examples are horrific, like another language.
https://docs.microsoft.com/en-us/windows/win32/learnwin32/wi...
https://docs.microsoft.com/en-us/windows/win32/learnwin32/ma...
char *
. Some people (me included) find it clearer to write the type, followed by the variable name. So just as you’d write int a
, you’d write char* a
.The fly in this ointment is that C type syntax doesn’t want to work that way. It’s designed to make the definition of the variable look like the use of the variable. A clever idea, but unlike nearly every other language, which BTW is why I think you should really use typedefs for any type at all complicated in C.
For example, the type-then-variable style falls down if you need to declare an array
int foo[4]
or a pointer to a function returning a pointer to an int int *(*a)(void)
(...right?).So I’m perfectly willing to do it the “C way”, I just find out more readable to do it the other way unless it just won’t work (and then prefer to use typedefs to make it work anyway).
Note that this was rethought for Go syntax.
char *a => *a is a char
char a[3] => a[i] is a char
char f(char) => f(c) is a char
char (*f)(char) => (*f)(c) is a char (short form: f(c))sizeof(a[3]) is not evaluating a[3], so it also isn't UB.
int const * const i;
This is nice because you can naturally read the signature from right to left. "i" is a constant pointer to a constant integer. It's a little unconventional, but I think it's a really clear way to convey the types.In the above, you're declaring the types of * a and * b to be char, making a and b pointers to char.
(EDIT: how do you escape * properly inline with other text?)
main(int argc, char **argv)
These all produce the same result main(int argc, char* *argv)
main(int argc, char** argv)
main(int argc, char* argv[]) char * a;
declares a variable, `a` that, when dereferenced using the `*` operator, will yield an int.In C++, the same line declares a variable, `a`, of type `pointer-to-int`.
C cuddles the asterisk up to the variable name to reflect use. C++ cuddles it up to the type because it's a part of the type. Opinions don't really differ on whether C-style or C++-style is better, but a lot of cargo-cult programmers don't bother adjusting the style of the code snippet they paste out of Stack Exchange so you see a lot of mixtures.
The difference is the function prototype `void newprint(char *, int);` at the start, which is missing in the second example. With the forward declaration, the compiler knows what arguments newprint takes and errors out if you pass something else. C is compiled top to bottom so in the older version of the example the compiler has no way of knowing what number of arguments the function takes at the point whree it is called. In (not so) old versions of C that implicitly declared a function taking whatever you passed it.
Because the parameter passing was uniform, you didn't need to inspect anything at the call site. All functions get called the same, so just push your params and call it. Types were for the callee and were optional. This is what powered printf, surely the highest expression of K&R C.
In modern C-lineage style, we enumerate our formal parameters, and variadic functions are awkward and rare. But LISP and JavaScript embrace them; perhaps C could have gone down a different path.
The interesting thing is that with K&R C a function declaration/prototype is optional. That means you can call a function that the compiler has not even seen. Mismatches in parameter/return types (which are optional and default to int in declarations as well) are normally not a problem, because of the aforementioned promotion. If you have the declaration, then the compiler will at least let you know about wrong number of arguments.
But I think it's good to know other pitfalls:
- The "default" return type of a function is int
- A function that does not use void to tell that it takes no argument just have an undefined number of parameters
See this example: https://ideone.com/GfwS4O
You could in theory use the address of a local variable (allocated on the stack) to access the arguments passed to the function directly on the stack.
But this is just madness... Or is it ? Isn't C just assembly with a "nicer" syntax ?
Here is the "modern" equivalent:
char combine(char s, char* t) {
As an aside it's not always more verbose because you can group parameters by type, eg:
int foo (c1, i1, c2, i2)
char c1, c2;
register int i1, i2;
{
... char *combine(char *s, char *t)As of the 2011 standard, that's still the case. I think that C2X will finally remove them.
modern syntax is:
char *Combine(char *S, char *T) {
...
}
which means the Combine function returns a string (char pointer), and takes as arguments two strings, S and T.