C genuinely less powerful than modern languages
So today I finally realized that C is not only less convenient than modern programming languages, it's also genuinely less powerful than modern languages: In garbage collected (and reference counted) languages you can easily write:
if (attr_list != priv->cached_attr_list ||
0 != list_compare (attr_list, priv->cached_attr_list)) {
priv->attr_value = parse_attr (attr_list);
priv->cached_attr_list = attr_list;
}
In modern languages this snipped works as expected: As soon as attr_list changes in some way it will be parsed again. In C this code doesn't work, since all pointers are just weak references: malloc/free don't know which memory address still are referenced, and therefore malloc easily will return some still referenced memory address. So destroying and rebuilding attr_list can easily lead to the new attr_list pointing to the same memory address as the old list:
old_list = g_list_append (NULL, "foo");
cached_list = old_list;
g_list_free (old_list);
new_list = g_list_append (NULL, "bar");
g_assert (new_list != cached_list); /* usually will fail! */
With C there are good chances that old_list and new_list both point to the same memory address. This clearly sucks.
Didn't expect that there are algorithms you can implement with modern languages, but not with C. Well, but obviously strong references/garbage collection do not only add convenience, they also allow new algorithms.
Yes, old news for the smarter among us, but not common knowledge I guess.
Update: I definitly should use a better phrase than "algorithms you can [not] implement", as I am definitely not here to challenge turing completeness of C.
Comments
Post a Comment
This site's webmaster failed misserably in upgrading the underlaying web framework.
The comment system is entirely broken right now. Sorry!
Your assert is just bogus. Period.
What you speak of is not a new algorithm, but a variant on implementation details. Please do not confuse syntactic aesthetics with power. C is a turing complete language, thus, by definition, there is no language with more power. Though I agree it would be better if you first example worked as expected.
Yes, C is such a pile of monkey crap that all your high level languages are implemented in it. I take it you've never compiled Ruby from source?
Are you suggesting that C is not Turing-complete?
Ouch. Don't blog things like this. It doesn't make you look very good. Church and Turing figured out this is wrong 70 years ago.
Equality of R-value doesn't mean equality of L-value. It's easy to get the same problem in Java, by using == instead of .equals(), but I guess you don't know much Java either. Odd, it's garbage-collected.
Hubert: Yes, in C or C++ the assertion is wrong. In other languages like Java, C# the assertion is correct. That's the entire point of the post.
Those pointing out turing completeness: Obviously the phrase "algorithms you can [not] implement" isn't quite exact to describe my observation, since obviously C is used to implement those high-level languages - the same way bricks are used to build houses. You also could replace malloc/free with some garbage collector.
Still that code snippet won't work in C without adding alot of glue code (aka. implement high-level language) or replacing some essential internals. Well, but maybe this is not different from the convience of automatic memory management.
Julien: Obviously you didn't get the point. Probably my fault my abusing the word "algorithm". Btw, that snipped above would work in Java. Guess how I got the idea to try such code in C? Maybe by reading "if (a != b || !a.equals(b))" too often?
this post is sort of insane.
in "a modern language" you wouldn't even be able to free the old list without unsetting the variable referencing it. you'd have to set old = null or something.
you're certainly right that you'd never be able to have (old == new) in a "modern language" but that's only because modern languages REMOVE additional power that you have in C (ie: the ability to free memory while still having pointers to it). if you CHOOSE not to use reference counting in C then this is your choice.
a gun isn't "genuinely less powerful" because you have the ability to shoot yourself. sorry.
ps: just in case your cached_attr_list is some sort of a weak pointer in your "modern language" (although you definitely don't go out of your way to say this):
if that's the case, you can always use GObject's weak pointer abilities to set it back to NULL when the object is freed. this is implemented quite exactly how weak pointers would work in almost any "modern language".
if you're not using a proper object at the end of your pointer then, yet again, that's another choice you've made in C that you wouldn't be able to make in other languages....
After you free old_list the cached_list pointer becomes equally invalid (since it points/pointed to the same memory), so you can't use it in a comparison. As you said C doesn't know if a pointer is invalid or not, so unfortunately you have to pay attention to that yourself.
Dear Mathias
If you don't like C it's your choice. But calling C less powerful, because it does not support some very specific construct, is imho a bad idea. Not every language is ideal for every task. C# for example is extremely well suited for creating a UI but not at all for high performance math or kernel programming. Such comparisons as you made them here make little to no sense.
"C# for example is [...] not [well suited] for [...] kernel programming"
http://en.wikipedia.org/wiki/SharpOS
and
http://research.microsoft.com/os/sing...
:-)
desrt: First of all it's not fair to call someone insane, just cause I reflect about some programming problem that seriously bothers me right now.
Back on topic: Yes, a weak reference should be used for caching the list reference. So the code should be something like this:
if (null != cached_attr_list.get () || !cached_attr_list.get ().equals (attr_list)) {
cached_attr_list = new WeakReference (attr_list);
attr_value = parse_attr (attr_list);
}
Problems:
- this entire API have to use avoids GObject [1]
- strings are no GObjects, nor even reference counted and won't ever be in our stack [2]
Yes, in the ivory tower of academica C is more powerful than managed languages. In the real world, when dealing with this specific real world problem, then C obviously is less powerful than modern languages. Well, or let's say "C is less suited for real world problems", if you want to reserve the relation "powerful" for measuring turing completeness.
Also note that the problem I refer to is real, that real that the authors of EContact for instance gave up on making that class behave sane when changing attributes. Look at this example:
c = e_contact_new_from_vcard ("...\nFN:Foo\n...");
g_print ("%s\n", e_contact_get_const (c, E_CONTACT_FULL_NAME));
a = e_vcard_get_attribute (e, "FN");
e_vcard_attribute_remove_values (a);
e_vcard_attribute_add_value (a, "Bar");
g_print ("%s\n", e_contact_get_const (c, E_CONTACT_FULL_NAME));
Instead of "Foo\nBar\n" that snippet will print "Foo\nFoo\n"...
[1] http://library.gnome.org/devel/libebo...
[2] http://mail.gnome.org/archives/gtk-de...
PS: Those who think I'd hate C and want to say my tool is better than yours, are that much offside, that I cannot express it. Seriously, I am really just a poor code monkey who realizes step by step that he is forced to use the wrong tool for the job.
Dear Chris
The singularity Kernel contains assembly and C++ code and there is bound to be some JIT somewhere. You still need non-CLI code. In the case of SharpOS this seems to be the AoT compiler. They need those low level languages and this implies that these languages seem to be better suited for certains purposes than C# or Sing#.
Dear Mathias
C might be the wrong choice for your problems, but it might be the right choice for someone elses problems. Your "real world" problems might not be other peoples real world problems. They are not mine for example. And I deal with "real world problems" every day. So please don't generalize.
Manual memory management requires more thinking when writing code, news at 11.
tbf: i never said that you were insane. but i still think that your post was.
as near as i could tell, you got bit by a bug -in some code written in C- because someone made an assumption that isn't true; you were frustrated and decided to rant through your megaphone about something that's not really true.
i'm certainly not talking about 'turing completeness'. i'm talking about something much more specific: the ability to implement this same algorithm in C exactly the same way as you would in any other language imperative "modern language" -- but maybe having to do a lot more of the work for yourself manually.. this is not something you could do with a turing machine, for example (or even many other modern, but non-imperative programming languages).
if you have the benefit that you can have weak references to strings in other languages then it's probably because those strings are objects: you can have that in C, but (as usual) you have to do it for yourself. it's a pain in the ass, but nothing new...
The entire point of C as a language was to have something as close as possible to cross-processor machine language. What you're thinking of as a feature, others would think of as pollution. You can use a library to have reference-counted allocations if you need them; but with C, you can also ensure you only pay the overhead when you need to, and not all the time.
Sorry you're having to have this issue - but seriously, you can implement your own reference-counted types with alloc/dealloc. It's not even hard.
So what language is the garbage collector written in?
No one said you never need/use reference counting with C. If you think about it there are some (or lots of) situations in which you can't decide when to free an object while writing the code. Those objects needs reference counting and you simply use reference counting and access the object indirectly (with a function call).
@Chris Burkhardt: One research project doth not a mainstream kernel make. It proves a particular language's suitability for kernel programming even less, especially when the _real_ kernel in that project is written using very traditional methods.
It's like saying that if you can implement a FUSE filesystem in Logo, that Logo is a language that is well-suited for systems programming. Clearly absurd.
I think the whole discussion here is a result of misunderstanding the word 'powerful'.
You probably meant 'power of expression' and (if that's the case) I totally agree with you.
In Python I can split up a string in one line with 'abc def ghi'.split()
I can do that in C and my 10 line implementation in C might run a lot faster, but it still makes Python a more powerful language as in 'power of expression'.
"malloc/free don't know which memory address still are referenced, and therefore malloc easily will return some still referenced memory address"
No. Malloc is smart enough to not return memory which has already been allocated.
To clarify: if there is unallocated memory being referenced by a pointer, then yes, there is a chance that malloc will reallocate that same memory space when called again. That shouldn't matter, however. You should never reference memory spaces that are not allocated; it is terribly sloppy programming that will lead to bizarre behavior, and, in some situations, crashing of the entire program (and entire system, if you aren't running in a protected memory segment).
Yes, you can destroy still-referenced memory. You just need to be smarter about destroying it in C than you would in Java.
If you mean that higher level languages allow you to be lazy and sloppy with your memory management then yes, that is so.
Finally, an algorithm is merely a logical set of steps used to achieve a goal. Translation of that algorithm into code can take many forms. Such translation is not limited to the ability of the language, but rather the ability of the programmer. Yes, old news for the smarter among us, but not common knowledge I guess.
So, the whole point of your post is that you can't code properly, so you blame C.
Quite interesting...