I am convinced there is some kind of long-term drinking game going on in the C standards committee to see how many different uses they can come up with for the word "static".
They don't really have a choice, do they? If you want to add features you can either: 1) make a new reserve word, possibly breaking existing code 2) reuse a reserve word in a new context.
C reserves part of the namespace for the language or implementation, so they have a third way, which they've used for e.g. boolean types in C99. Make a new reserved word in the reserved namespace (either starting with __ or just _ followed by a capital letter) and then add a standard header to #define or typedef the unfriendly reserved word to something nicer for code that wants it.
Managed C++ was chock full of __keywords for .NET memory management - like Apple's ARC in the worst case, except there were no good cases. While it probably was a clean superset of C++, it was painfully hideous.
CLI/C++, by which Microsoft replaced Managed C++, added new operators and keywords without much regard for backwards compatibility (=without underscores).
The implication being that people value code readability over __backward __compatibility. I have no data on the popularity of Managed C++ vs CLI/C++ though. I was only a fanboy because CLI/C++ looks awesome and I generally enjoy Herb Sutter's work, but then got sucked into the Appleverse.
Just adding __ keywords is not the approach I described, though. I described adding __keywords, and adding a standard header that redefines them into non-underscore keywords. This avoids breaking old code (it won't include the header) while allowing new code to use nicer keywords, if it wishes.
Right - I was just trying to provide context :) If there is anything in the Managed C++ -> CLI/C++ transition that is better than the ISO C approach, it is maybe the contextual keywords in CLI/C++ - something that can't be achieved with #define.
Deleted my previous comment because it was wrong (I was saying that it didn't help with the names of subroutines). It appears you can even have subroutines named after keywords, but only as long as you put "&" in front of it when you call it:
sub while
{
print "It works!\n";
}
&while();
I guess it is still the case that you can break perl code by adding keywords, if it collides with a subroutine that is called with the "&" symbol. Seems most code doesn't use "&" though, but it's a relatively quick fix.
I believe the @ is used only by the obj-c keywords and not the ones from C. So it'll be something more like this
@autoreleasepool{
for (int index = 0; index < 100; index++){
int error = [MyObject performSelector:@selector(addCoolFilterToImage:)];
if (error) {
return 32;
}
}
}
Or you can add context-sensitive keywords, which are keywords only where it’s syntactically valid and identifiers elsewhere. Those two contexts are almost always mutually exclusive, and it’s obvious which one you’re in. ActionScript 3, though absolutely not a good example of language design in general, has a good example of this. You can say:
It looks like a syntax error to me. You can add numbers in array index brackets so a plus sign already has meaning there. Considering that a[10+] is just one character off from a[10++], it seems to me that this is just a bug waiting to happen.
It would be a syntax error. . . unless you add syntax for it.
As far as it being a bug waiting to happen, C already has plenty of traps like that - '=' vs '==', '&' vs '&&', and so on.
In terms of grammatical clarity, I shouldn't think it would be any more confusing than any other operators that have both unary and binary versions. By my count C already has three of those: '-', '&', '*'. The latter two are even examples where the unary meaning and the binary meaning are wholly unrelated.
Besides, it's a way of using + that is already well-established in everyday idiom. And there are semantically-related uses for + that are already well-established in computer languages, too - the Kleene +, for example, should be familiar to everybody.
In terms of how it would work for the grammar I realize this would be somewhat of a departure since it's repurposing a symbol that's normally an operator to be used in a manner that would be more like a keyword according to C syntax. . . but I think that's a detail that should be much more interesting to standards committees than it is to people who primarily just use the language. And in terms of the human factor I submit that it's much more workable than taking a word from the English language and repurposing it to mean something that's more-or-less the opposite of what it means in English.
I never ever had problems with it in C. I actually had a problem with it in another language (might have been a BASIC) where = was contextual, so one could do "a = 10" and then somewhere down the line "if x = 5 then". I used == and couldn't figure out what was wrong for nearly 5 minutes (I think it was recognising (or not) as an unexpected token).
= and == should have different meanings.
I also like how JavaScript has ===, although it is a little superfluous.
The problem with the plus sign is that people have a natural assumption about what it means (that is, the addition). Changing that make things more confusing. At least, C programmer are used now to the fact that the static keyword can have strange and different meanings and another one is no big deal.
That looks nice, but I think it could be simply void bar(int myArray[10]), since C effectively ignores the number inside the first pair of brackets in a parameter list, they could have added some meaning to it. No need for another meaning for "static"
(off topic) I'm sure that was true at some point, but the comment is out of date now. To my surprise, if you download the current bash source code from [1], it actually has a yacc grammar -- see parse.y. It's 6000 lines, but it would be less intelligible without yacc I'm sure.
Honestly, it might be Stockholm syndrome, but I don't find bash hard to parse at all. The only real problem I see is that people get in trouble when they try to nest quoting styles. But there's basically never any reason to do that. User-defined functions basically eliminate this (may not be POSIX compliant, but supported in all shells I know of),
I factor all my shell scripts into 2 to 10 line functions and they are super easy to maintain and are 5-10x shorter than any alternative.
User-defined functions basically eliminate this (may not be POSIX compliant, but supported in all shells I know of),
User-defined functions are part of POSIX, but the function keyword is not. This bit me when I was porting a script from bash to dash (worth the effort on slower devices, as dash is significantly faster loading).
On bash you could do something like this:
function foo()
{
echo "Foo"
}
In POSIX shell (and, thus, also bash) you do this:
That is a comment on the style of C that Bourne used in his shell implementation. He used the pre-processor to e.g. name { BEGIN, } END and do other Algol-isms.
Duff would apparently rather read C that was written in C.
It's not at all the same situation for C. C's grammar is very-well defined and not at all hard to parse.
C++, on the other hand, has a horribly complex grammar. It would be unfair to say that "nobody really knows what it is," given that it has been standardized. But I think you will find that many tweaks to your code are needed when going between different C++ compilers.
I think what a lot of commenters in this thread are missing is that while the concept of a highly context-sensitive grammar seems simple, the reality is not. And having such a grammar makes it virtually impossible to have good tools.
Before some pedant reminds me that C's grammar is also context-sensitive: yes, I know. But you can parse C with lexx and yacc anyway, whereas you have no hope of doing this in C++.
I meant more on the programmers side than the implementors side. [0] But that's probably a moot point anyway, as I understand it most C code is probably crap plagiarized from amateur tutorials.
[0]: Nobody would here be an obvious exaggeration, as evidenced by the above blog post.
I think you are getting mixed up. The grammar of C isn't complex. Unrelatedly, C does have a few lesser-known features, but remarkably few for a 40-year-old language.
I never got into C because of books like "Expert C Programming"[0], knowing they exist tells me that theres a ton of "gotchas", and life is too short for that if I'm not really crazy about it in the first place.
Then again, as far as actual grammars go, I've heard C++ is bad enough that the compilers are the standard, and that if you want to be "compliant" with real world C++ code you copy every feature [1] of GCC.
Almost every language has a book titled "Expert $LANGUAGE". If a language doesn't have one of those, it's probably either very new, or nobody actually uses it, or both.
C and C++ are good for doing low-level stuff. If you don't want to do low-level stuff, then you should not worry about them. But in that case, you might also want to avoid commenting about them :)
clang is copying gcc's features because most of them are good features, and standards bodies move slowly. This is kind of an odd thing to criticize C++ for, since a lot of newer languages don't even have a standard, but just a reference implementation. It's hard to criticize your neighbor for living in a tent when you live in a sleeping bag.
>Almost every language has a book titled "Expert $LANGUAGE". If a language doesn't have one of those, it's probably either very new, or nobody actually uses it, or both.
I said books. I got that vibe in general from the people I met who claimed to be C wizards. Incredibly offputting.
>C and C++ are good for doing low-level stuff. If you don't want to do low-level stuff, then you should not worry about them. But in that case, you might also want to avoid commenting about them :)
I was going to delete my original comment because reading it over it felt like a bad idea. (I don't like jumping into ignorance induced shitstorms.)[0][1]
>This is kind of an odd thing to criticize C++ for, since a lot of newer languages don't even have a standard, but just a reference implementation.
I should have put "features" in quotes. I was specifically using the definition in the old jargon file. [2] So what I really meant to say is that the compilers end up supporting each others bugs for compatibility reasons.
As for reference implementations, if the reference implementation is for all practical reasons the only implementation, then you don't need a standard.
[0]: The comments I got in response are interesting enough that I'm actually glad I didn't.
[1]: EDIT. My ignorance.
[2]: See footnote on the last post, I should have made that more clear or used different terminology.
There was an "I heard" in that original context. Back in the great grand parent post. I might be able to find some examples, but since I don't have one off hand I'd rather drop the conversation.
So that's what I'm going to do unless I suddenly get the urge to go running through clangs commit log.
This works with pointers in both C and C++. int (*foo)[10] is a pointer to an array of exactly 10 elements. The novel thing being pointed out in the OP is the "10 or more" aspect.
That's true, until you forget and type foo[i] instead of (* foo)[i]. Of course, compilers will usually catch that mistake at compile time...
There'a an interesting analogy with structs here. K&R invented the -> operator specifically to obviate (* ptr).member. It's too bad that arrays took a different route through C's history and didn't manage to end up in a place where a similar convenience would make sense.
Correct. The key difference is that the enable_if version can be overloaded with mutually exclusive requirements:
template <size_t N>
typename enable_if<(1 < N && N < 10), void>::type
foo(int (&bar)[N])
{
// called when 1 < N && N < 10
}
template <size_t N>
typename enable_if<(N >= 10), void>::type
foo(int (&bar)[N])
{
// called when N >= 10
}
OTOH, the key advantage in static_assert is the meaningful error message.
I don't think that is more readable. With enable_if the precondition is clearly visible at the function header, while the static_assert is inside the body and thus more easily accidentally ignored.
On the other hand, static_assert will have a more helpful compile error for users of the code. They will clearly see assertion condition that failed, rather than missing candidate error due to substitution failure (or worse, a different overload silently considered instead).
... and just as I was going to make a smart-ass joke about Boost probably having another contrived contraption just for that - there it is, already sneaked into the standard too.
I can confirm. Perhaps this is a bug? As per the C99 standard [Clause 6.7.5.3: Function Declarators, point 7]:
A declaration of a parameter as "array of type" shall be adjusted to "qualified pointer to type", where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation. If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression.
Since that's a 'shall' declaration, shouldn't it at least throw out a warning?
This "shall" is not listed under "Constraints", so violation of it is undefined behavior and does not require a diagnostic. See 4 (Conformance):
If a ‘‘shall’’ or ‘‘shall not’’ requirement that appears outside of a constraint is violated, the behavior is undefined.
This is an area where a compiler can (sometimes) see violations, though, so I think gcc should diagnose when possible, in the same vein as format specifier mismatches in printf().
It's not bad at all, at least if you've made the conscious decision to write GNU C and not std C, and accept that non-gcc compilers (except maybe clang) may not be able to compile your code.
Unfortunately, though, I believe one of the -std=gnuXX variants is the default, so most people don't make that a conscious decision.
Can someone recommend a resource that talks about some interesting C stuff similar to this? I've looked at "Expert C Programming: Deep C Secrets" but found it a bit outdated. And the C standard is a bit dry :-).
You might look at 21st Century C: C Tips from the New School by Ben Klemens. It's very new (November 2012), and has some really nice stuff in it.
It's getting mixed reviews, but I really found it useful (even if I, like others, disagree with some of his tips). It's particularly good at sorting out what you can do in C99 and C11.
[edit: He has a really useful section on sorting out the different meanings of "static" in C, though I don't recall this being one of them.]
The International Obfuscated C Code Contest (http://ioccc.org/) has some real gems.
Compiling another language to C is good way to learn a lot about C's nooks and crannies.
You could also look at C coding standards such as the MISRA guidelines. While many things they complain about should be obvious, there will inevitably be some really obscure things they urge you not to try.
Unfortunately, gcc does not warn. I can pass a NULL pointer and arrays that are too small. But on the other hand, there is very little use, because arrays usually contain an arbitrary number of elements. This might be useful for matrices and vectors, but then I would rather wrap them in a struct anyway.
This is one of the things that attracts me to Go (and developing tools for working with it, which requires parsing the language, etc.). It's much easier to keep the entire language spec in your working memory, because it all fits in http://golang.org/ref/spec.
I agree that it's a pretty clean language, but it still has oddities. For example, where in your linked spec does it address why the following trips a compile error?
func foo(b bool) {
if b {
return
} else {
return
}
}
func bar(b bool) int {
if b {
return 100
} else {
return 200
}
}
func baz(b bool) int {
if b {
return 100
}
return 200
}
func qux(b bool) int {
if b {
return 100
} else {
return 200
}
panic("?")
}
// Error: function [bar] ends without a return statement
To be fair, they have executed the overall project very well from what I've seen. A few things falling through the cracks is probably inevitable. That issue's not closed so there's hope for it yet.
Providing a link to the issue in the bugtracker, with discussion, is definitely "showing something".
I don't know, maybe I'm just weird, but when I see a comment replied to with a single link patterns suggest that it's a link disproving something. So I got confused until I had read the full page.
Is seems to be a bug but it could well be a feature. Requiring that functions end with a return statement is a good thing IMO. That avoids weird bugs if later the "else" is replaced by an "else if".
C and even C++'s spec are still easier to remember than bash shell scripting imho. For bash shell scripting, I need to keep a document full of examples to copypaste from, otherwise it's always wrong on first try anyway!
That's just because its young. The same could have been said for C in the long long ago. And honestly compared to most of whats out there, C is a tiny language. Give go time, it'll bloat ;)
Not only because it's young, also because we _do_ learn from mistakes made in other languages.
For example, I do not see PL/I's choice to make none of its keywords reserved names (IIRC, defended on the argument that one cannot expect anybody to know all the reserved words) repeated much anymore in Algol-like languages.
As another example, IDE/language pairs such as Eclipse/Java have learned us the advantages of languages that are easy to parse and where there is little ambiguity even in incomplete or incorrect programs (it makes syntax coloring easier, and allows for refactoring tools that are somewhat reliable on non conforming source code)
Whether generics get retrofitted will be the measure of this. I suspect they won't, because by the time a Go 2.0 timeframe arrives, the thinking will be something like "Time has shown that we thrived without them, so despite commentator gnashing, they're simply not required."
It's possible to write tools that generate code instead of building it into the language too. I wonder if it can be done better than building it into the language.
C preprocessor stuff works with text and text only. That's why it's quite hard to make things elegant. Tools can work with text and anything else you can think of. So they can potentially be better. But I don't know what could be done at this point (in fact, this might be a good problem to be analyzed from a theoretical side).
What makes me wary of using these uncommon constructs is that someone editing code later may make a change based on a superficial understanding (and break the build)
After the fifth time dealing with subordinates misunderstanding template constructs I decided to throw out all of the C++ code and reimplement in "simple" C and x64 assembly -- at least now people don't mess with the assembly
It's not that easy, I'm afraid. Languages do differ in many areas and one of them is how easy it is to make a mistake in one.
I think Go was designed with this in mind? Also, Haskell's type system guards against this. On the other hand C does nothing to prevent you from shooting yourself in the foot and C++, while improving some things, makes it overall worse because of sheer amount of constructs in the language.
Ruby and JS are better in that they run on VMs and so won't segfault (that often), but other than that they do very little to help avoid making mistakes (implicit undefineds passed to functions in JS...).
Anyway, languages are not created equal and one thing a language designer can optimize for is to reduce the probability of programmer making mistake. That's only one of the variables however and sometimes it's the other goals that are more important and then we get languages like C++. That's not to say it's bad, it's just optimized for different things.
Why do many features added to C after the first standard have to be quirky and slightly incompatible (with C++, with existing implementations, etc.) like this?
Other examples are inline (different from C++, makes use of weird combinations with static and extern) and tgmath (compiler magic inaccessible to user-defined functions until C11).
They also seem to __barely__ improve the language without ever being "cool" or "interesting".
At least C++ has some standard data structures...
PS: Even Python has binary literals, while they were deemed "not useful enough" for C.
6.7.5.3 point 7: “If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression.”
Interesting, but the one comment I haven't seen yet on this thread is 'Why is this useful enough to be a compiler feature?'. I think this is especially relevant with a relatively slim language like C.
In the rare cases you need to do this sort of check why not just write a simple sizeof test?
The compiler doesn't pass any size information when you pass an array into a function. The function just gets a pointer. If you take the sizeof the array, you'll get the size of a pointer on your system:
[eric@rangely foo]$ cat foo.c
#include <stdio.h>
int test (int arr[10])
{
printf ("%lu\n", sizeof (arr));
return 0;
}
int main (int argc, char *argv[])
{
int arr[5];
test (arr);
return 0;
}
As another poster said, the compiler can't know inside the function body the size of the array passed if you just use sizeof.
More to a style/safety point, if I ever see a function that expects an array and doesn't also take the size of that array as another parameter, that's a bug waiting to happen.
Especially in this case since this feature seems to only throw a warning on recent versions of clang, and more or less nothing else.
Wouldn't you need to put your sizeof test outside of every call to your function? Inside the function it would only know the declared parameter's type, so it would have no idea what sized array you actually passed.
Using 'static' in this way compiles with both Oracle Studio 12 and IBM xlc 11, but neither exhibit the behavior that gcc is shown to have in the article. Passing in both NULL as well as an array of shorter length work just fine with no warning/error from the compiler.
According to section 6.7.5.2p1 of C99: “If the expression is a constant expression, it shall have a value greater than zero.”
The “expression” here refers to an expression in between [] in an array declaration; so the declaration of size 0 is a constraint violation and requires a diagnostic. You can get gcc and clang to issue a relevant diagnostic with “-std=c99 -pedantic”.
A whiff of C++ would be fine, as long as they don't take enough to make people think they have to pick a subset of C. One of C's strengths compared to C++ is that C is one language with a small number of dark corners, not multiple languages trying to share a single standards document.
Another advantage of C over Old C++ is that there's one C, defined by one standards document and maybe a few common features that are not standard but reliably present on most compilers and hardware. There isn't a list of features you're theoretically able to use but the compilers don't support (templates in Old C++) or common things you do differently in every implementation.
Pascal figures in this in that the official standard Pascal was basically unworkable as a language, due to features like the size of an array being an obligatory part of its type and the resulting lack of a way to write a function that could handle more than one size of array. Having it be optional, as we see here in C, is really the only way to go unless an array knows its own size and won't let you overstep the bounds.
> Another advantage of C over Old C++ is that there's one C, defined by one standards document and maybe a few common features that are not standard but reliably present on most compilers and hardware. There isn't a list of features you're theoretically able to use but the compilers don't support (templates in Old C++) or common things you do differently in every implementation.
I had to develop with multiple C commercial compilers between 1999 and 2002, across several OS. The code had quite a few #ifdefs because of them.
Are you aware that C11 has optional features?
> Pascal figures in this in that the official standard Pascal was basically unworkable as a language, due to features like the size of an array being an obligatory part of its type and the resulting lack of a way to write a function that could handle more than one size of array. Having it be optional, as we see here in C, is really the only way to go unless an array knows its own size and won't let you overstep the bounds.
The first ISO standard yes, but all Pascal dialects always had feature parity with C, while being more type safe, fast compile times and direct support for modules.
Most of it was made part of the ISO Extended Pascal standard, which most people ignored as the industry cared more about Turbo Pascal compatibility. Both solve your arrays example.
The common complaint that the stronger type checking languages impose performance penalty with arrays bound checking is always wrong, as the compilers allow for it to be turned off.
In both cases it is the problem with standards and vendor differentiation, you seldom get a 100% compliant implementation of any standard.
Your statement seems meaningless. It's possible to have safe code in any language (even assembly language) as long as "everyone on the team plays by the rules." The question is, how hard are the rules to understand and how obvious are deviations from them? C++ has more rules and less obvious behavior when you deviate from them, so it's strictly less safe than C.
The advantage of C++ was always that it enabled (slightly) more rapid development than C.
std::vector isn't "safe." If you're using a std::vector::iterator and someone appends to the end of the vector, your iterator may be invalidated. std::string isn't safe either. It's easy to create references to strings that don't exist any more, by returning a const reference to a string and then later deleting the string. smart pointers aren't safe-- partly because of cycles, partly because of references to smart pointers, partly because you inevitably have to convert them to something else to use them. I've been using C++ for years and I've debugged all these problems.
> I've been using C++ for years and I've debugged all these problems.
Me too, my first C++ compiler was Turbo C++ 1.0.
They are a lot safe than using the C direct pointer manipulation idioms that make it so easy to create insecure code that can explode at any moment.
What STL offers might not be 100% as safe as the Pascal family of languages offer among others, but it sure is a way lot better than using plain C idioms.
The problems you describe are quite easy to spot if a static analyzer is made part of the build.