Slashdot Mirror


User: betterunixthanunix

betterunixthanunix's activity in the archive.

Stories
0
Comments
6,598
First seen
Last seen
Profile
(view on slashdot.org)

Comments · 6,598

  1. Re:One good reason... on What's To Love About C? · · Score: 1

    Deal with them in the destruction; abort execution with an unexpected exception if handling the error is found to be impossible, because if your class that has knowledge of its own internals and the resources that it represents can not free its resources, nobody else can.

    OK, but if the error can be corrected, then the destructor can proceed. Are you suggesting that the class should know both how to deallocate itself and how to correct external errors?

    Except destructor exceptions aren't supposed to be caught at all.

    Which means that there is a class of errors that the clients of the class will never have the ability to handle. Even something as simple as logging errors cannot happen under that rule.

    I never claimed that you shouldn't sync in a destructor, I claimed that you shouldn't throw exceptions if it fails, because destruction time is already too late to expect synchronization

    Then why sync at all? If it is not something that should be expected to work properly, then it is not something that should be done.

    Feel free to do all the damage control you think you should do in the destructor, just don't expose your users to its hazards! That's wrong!

    I am not seeing why that is any more wrong than throwing an exception from a constructor or a member function. We do not typically catch an exception, then say, "OK, time for this object to be destroyed, because it is in an invalid state!" If your disk is full when you call write(), and you might correct the error, then you call write again; you do not have to throw your hands in the air and destroy the object. Yet you are claiming that when it comes to destructors, things should be different -- not just that things are different, which we agree on, but that the difference is justified.

    I disagree; the difference is not justified, and the fix is not incompatible with the purpose of a destructor. The fix is to support restarts, so that an exception handler can allow control flow to return to a point defined by whatever threw the exception. If the error can be corrected, there is no reason for the destructor to not finish, but it is wrong to expect the destructor to know how to correct errors. The error might have come from some function the destructor called; why should the destructor know what to do about such errors?

    The approach you are suggesting is that if the destructor does not have any good way to deal with an error, the destructor should just fail silently. Even a global error flag is better than that.

    Asynchronous resources sacrifice reliability for performance. If reliability is that important to you, make your interface synchronous.

    That is a false dichotomy. We can have both reliability and performance, by allowing errors to be corrected. The ugly way to do that is to enclosing synchronization routines in loops, and to have catch blocks that take corrective action and then resume the loop. The not ugly way is to support restarts, so that error handling code can resume the execution of whatever code threw the exception once the exception has been corrected.

    Wrong premise: the point of an exception is to interrupt the flow of code and signal a condition in which an object has entered an invalid state; anything beyond that is bullshit.

    So off the bat, you just dismiss the idea that you could ever correct the error and continue the execution of whatever code caused the exception to be thrown. If you have an object that will write to a file, and you catch an exception that indicates the disk is full, what will you do? How will your code be anything other than an ugly mess of loops and error handling code that is scattered all over the place?

    Just because a callee signaled an invalid state doesn't mean

  2. Re:One good reason... on What's To Love About C? · · Score: 1

    My point is that freeing that resource should not throw an exception

    OK, but sometimes errors occur when a resource is being freed, and there needs to dealt with somehow. What are you suggesting happen in that case?

    if you really must throw something then don't expect it to be caught

    That is true of exceptions in general, regardless of where they are thrown. That is one of the downsides of exceptions: there is no guarantee that the program will even continue to run after an exception is thrown, even if the exception is thrown from a non-destructor.

    If the user of the class is really interested in making sure that all the data has been flushed (or in handling exceptions if it isn't), they should call sync() before deleting

    Unless the object is deallocated because the stack is being unwound, in which case the only guarantee you have is that the destructor will be called.

    "Effects: Destroys an object of class basic_filebuf. Calls close(). If an exception occurs during the destruction of the object, including the call to close(), the exception is caught but not rethrown (see 17.6.5.12)." [27.9.1.2p5].

    OK, I must have been looking at something out of date; sorry about that. The point was not about the standard allowing an exception to propagate out of a destructor, however; the point was that the destructor for basic_filebuf will flush its buffer and then close the file, something you are claiming is a bad thing to do.

    That being said, if flushing or closing the file causes an error, the standard essentially says that the error should just be ignored -- no exception will be thrown, and there is not even a global error flag that can be checked. How is that a good thing? That is on the level of Java's approach to double exception faults (when exceptions propagate out of finally blocks), which is have one of the exceptions just disappear. The entire point of exceptions is that they cannot be ignored; allowing exceptions to just stop propagating or having a standard the requires errors to happen silently is a violation of the entire premise of exceptions. We might as well just stick with global error flags and checking return values if that is what we consider to be acceptable.

    Once again, the right answer is for the stack to be unwound after the exception is caught. If the user does not want to try a restart, they can at least have a chance to catch exceptions that are thrown during the unwinding process.

    I will ignore the rest of what you wrote since on top of having completely destroyed your line of thought here,

    You have done no such thing. All you have done is reiterate your view that there is never a good reason for an exception to propagate out of a destructor, changed your argument about flushing and closing files in a destructor, and promoted the idea that errors should happen silently. All that just to defend the C++ approach to exceptions -- and all that despite the fact that better approaches have already been implemented and have proved their worth in other languages.

  3. Re:One good reason... on What's To Love About C? · · Score: 1

    The moment you start passing structs as arguments and calling functions that are declared outside of the struct that is supposed to be your object you can no longer talk about object oriented programming because there really is absolutely no relationship between the function and the object

    There is such a relationship: the function expects its first argument to be an instance of the struct. Object oriented programming is not limited to using languages with explicit object oriented constructs.

    Furthermore constructors, destructors, inheritance, polymorphism, and encapsulation are all missing features in addition to the already mentioned lack of self-awareness

    Wrong on all counts; all those things are possible, they are just not performed or enforced by the compiler:

    • Constructors and destructors must be called by hand as part of the process of allocating the object.
    • Inheritance can be accomplished by giving each struct a pointer to its subclass and superclass data.
    • Polymorphism can be accomplished by create an array of pointers to functions, which can be overridden.
    • Encapsulation is accomplished by only using the functions you defined to interact with the class

    The method that I described, in the other hand, can be regarded as an actual feature of the language

    No, it is just a hack using RTTI. You might as well claim that using a textbook visitor pattern means that you have double dispatch; the effect is the same, but you are using other language features to compensate for / implement a missing feature.

    The library is part of the language, and one of the strengths of C++ is that, by not being part of the core, these features are both optional and replaceable by custom versions.

    Except that you still need some set of core language features. The library can only go so far in compensating for inherent drawbacks in the design of the language.

    There is absolutely nothing wrong wit this, choice is a good thing, and the only reason why you feel uncomfortable with this is because your functional background has limited your mind to a more restrictive set of storage duration constraints.

    Actually, I have a C++ background -- most of the code I have written in my life has been C++. I just do not think that C++ is that great of a language; debugging C++ code often involves debugging the mechanics of a program, rather than the logic i.e. locating a dangling pointer, figuring out where an array bound is not being checked, etc. Programmers have spent countless hours dealing with these issues, there have been countless bugs and security vulnerabilities resulting from bad program mechanics, and most of the time the software in question did not need to be written in a low level language. Even in cases where the software does need to be written in a low level language, there is no excusing a language whose standard does not explicitly forbid statements that cannot possibly produce correct code, and there are numerous such cases in C++.

    As I have explained in the previous post, which you pretty much ignored for whatever reason

    I have not ignored anything. You have simply dismissed the idea that exceptions could be recoverable, despite having clear examples of situations where recovery is both possible and appropriate.

    Destructors are not the place to throw exceptions, if nothing else then just because static and thread_local storage durations create situations in which you can simply not catch exceptions from destructors

    Why does the language allow exceptions to propagate out of destructors at all then? Requiring destructors to be noexcept is meaningless when there is no requirement that exception specifications be declared or followed. Destructors can and do call other functions, and those functions might t

  4. Re:One good reason... on What's To Love About C? · · Score: 1

    A destructor is a deallocator, it's not supposed to synchronize or guarantee anything else, only free resources

    And how is closing a file not freeing a resource? Here, from the implementation of fstream on my own system, is a destructor that closes a file:

    virtual ~basic_filebuf() {this->close();}

    Here, from the C++ standard itself:

    virtual ~ basic_filebuf ();

    3 Effects: Destroys an object of class basic_filebuf. Calls close().

    Yes, in case you are wondering, the C++ standard does require the close() member to flush any unwritten data and that it have the effect of closing a file:

    basic_filebuf * close ();

    Effects: If is_open() == false, returns a null pointer. If a put area exists, calls overflow(traits::eof()) to flush characters. If the last virtual member function called on *this (between underflow, overflow, seekoff, and seekpos) was overflow then calls a_codecvt .unshift (possibly several times) to determine a termination sequence, inserts those characters and calls overflow(traits::eof()) again. Finally it closes the file ("as if" by calling std::fclose(file )).323) If any of the calls to overflow or std::fclose fails then close fails.

    Since this pretty much kills your line of thought,

    Solid argument you have there -- restate your personal definition of a destructor, then declare that I must be wrong if I disagree. I am just going to guess that you have not spent much time reading the C++ standard, though, since your argument applies equally to the C++ standard itself.

    I see no point in replying to the rest of your post.

    No, you are just unable to come up with anything better than a definition of destructors that precludes double exception faults, and a definition of exceptions that precludes restarts. You might as well have stated your argument more concisely:

    I think betterunixthanunix is wrong. Therefore, I am right!

  5. Re:One good reason... on What's To Love About C? · · Score: 1

    If the user asks for the result of an action, they are expecting the result of that action, not an error.

    Except that there might be an error, which is the point of exceptions. I agree in principle that exceptions are a good thing -- I just think the C++ implementation is bad, mainly because there is no way to know if any exception will actually be caught even if you have a catch(...). The double exception fault basically reduces exceptions to Unix signals -- you should never rely on exceptions for cleanup, nor should you even rely on the program continuing to execute at all after an exception is thrown.

    OOP is a synchronous paradigm; and the case that you are describing is asynchronous

    How is that? For simplicity, you can just assume that your program is the only program in the entire system -- imagine that we are back in the days of DOS. That does not mean that you will never have an uncommitted write; you might be using buffered I/O, and the buffer will need to be flushed when the file is closed.

    If closing the file fails, it's up to you to try again until it succeeds

    Which basically means that you need to have try blocks with a single function, in a loop, and that the catch blocks are responsible for breaking out of the loop. You might as well revert back to using return values if you do that.

    If you can't handle that problem, then how are you expecting the user of your class to handle it?

    By giving the user a chance to skip over the error, if that is what they want to do. Again, in some programs, it will be better to just ignore that error, but in others it will not be -- and it may even depend on which part of the program you are in. Maybe I am willing to ignore exceptions thrown when i try to close a file, but only when I am already in a catch block for some other exception.

    For example: while a write error for lack of space exception should be handled and displayed to the user (as this error results from conditions unrelated to your program), a write error due to your use of an invalid file descriptor should not (because this error signals a potential bug in your code).

    I agree, some errors are more serious than others; but if the write error is a result of lack of disk space, that program should not abort because a file could not be closed as a result of lack of disk space.

    How can you finish the destruction of that object now that it is partially destructing while ensuring that its destructor (which was clearly not designed for this) won't tap into undefined behavior will correctly destroy the partially destroyed object?

    By resuming execution from a restart point defined by the exception itself. This is where having a well defined exception class hierarchy is necessary: you can have a "RestartableException" type, which should only be thrown in cases where the error is not a result of a bad program state (i.e. it should not be thrown for null pointers or divisions by zero, but it should be thrown if there is an I/O error, since that is something that can potentially be corrected). So, for example, a hypothetical RestartableIOException would have a special member function that causes control flow to return to a point where the operation can be tried again -- in the case of a write, perhaps that would cause the write to be attempted again, so if the error was a full disk and some space was freed by the user, the operation can proceed. It might also make sense for some exceptions to have a "skip-to" point, which would allow a single operation to be aborted e.g. to allow a more important operation to finish before a program exits (e.g. maybe you need to explicitly roll back a transaction in a database before quitting; errors from the logging system should not stop you).

    This would also resolve the destructor issue. A call to close in a destructor sh

  6. Re:One good reason... on What's To Love About C? · · Score: 1

    OOP requires self-awareness (i.e.: a this pointer), and C does't have that

    So you manually pass that to all member functions, like you would in Objective C or CLOS. You pretty much do that in C++, it just looks prettier:

    Classname obj;
    obj.member(arg);

    In C, that just looks a bit uglier:

    struct Classname obj;
    Classname_constructor(&obj);
    Classname_member(&obj, arg);

    You can manually implement a vtbl for single dispatch, or even a more complicated structure for multiple dispatch, if you want. Yet nobody would claim that C has OOP facilities, just like nobody should claim that C++ does not lack multiple dispatch.

    thus end up with code that is more error prone because mistakes that could have easily been spotted at compile-time

    ...oh the irony...

    int * x = 0;
    x[0] = 5;

    the language specifies smart pointers

    They are not required, even for closures. The use of a smart pointer in C++ is just a "nice" way to manage allocation and deallocation; it is not something that the language itself provides, it is provided by a library. That is basically working around the fundamental problem, which is that the environment of a closure and the closure itself are allocated, managed, and deallocated separately. The fact that you can destroy the environment of the closure but continue to use the closure is the problem here. It is like calling an object's destructor, but continuing to use the object after that.

    As a matter of fact you should actually be using at least unique_ptr to make sure that RAII takes proper care of the destruction of dynamic objects when an exception is thrown.

    Why, then, is that not a requirement? Why not require, at the very least, that all pointers to objects be smart pointers? Or perhaps just objects that are captured by a closure? I do not understand this philosophy of, "Well it is your fault for failing to adhere to best practices!" while simultaneously designing a language where the best practices are complicated (whole books have been published about how not to screw up C++ programs) and easy to ignore.

    Destructors should not throw exceptions

    Yet nothing stops that from happening. There is no requirement that destructors have a catch(...) block, nor is there a requirement that destructors have an empty exception specification (or that any exception specifications exist anywhere).

    Exceptions are meant to signal error conditions in which an action performed on an object causes it to enter an invalid state, and destroying an object should never cause that because, well, the object is being destroyed

    As I said elsewhere (maybe the same thread? sorry if we are arguing in two places at the same time), that is not true. An exception could be thrown as a result of a file being closed, and there are numerous other cases where routine things that happen in destructors can throw exceptions for things that cannot be controlled by any member function. Not all exceptions are fatal, and that includes exceptions that are thrown within a destructor.

    Destructors in C++11 are noexcept precisely because handling a destructor exception is as pointless as handling a SIGSEGV

    No, it is because exceptions in C++ are implemented the wrong way. The stack should not be unwound until the exception handler is located, period. Unwinding the stack first is what creates this messy situation with exceptions that propagate out of destructors. The C++ standards committee spent too much time listening to compiler authors when it came to this issue; unwinding the stack first is easier to implement efficiently (unwinding after

  7. Re:because - on What's To Love About C? · · Score: 1

    I would not say that C is a "great" middle ground. Ambiguous statements are not a requisite of a small language. Allowing pointers to be used uninitialized is not a requisite either. Allowing a non-void function to have no return statements is also not a requisite. There is no legitimate reason for any of the above, they only create problems and make debugging harder. We should not be relying on compiler warnings (non-standard), we should have a language that does not allow us to do things that never make sense.

  8. Re:Maybe because it compiles down to the metal... on What's To Love About C? · · Score: 1

    You know there are no pointers which reference an improper address because you are the one who assigned the address.

    Which is great if you never make mistakes. Except that people do make mistakes, and one dangling pointer can cause other pointers to be incorrect i.e. by overwriting their value. Debugging a pointer error is a giant pain, and it takes time away from debugging things that compilers cannot just do for you (i.e. errors in design or in program logic).

    You know that your array is large enough because you either allocated an appropriate amount of space, or assured that you only read enough from your input to fill your static buffer.

    Yet buffer overflows remain a real-world problem, even in code written by experts with years of experience.

    You know your string is null terminated because you put the terminating null there.

    Unless, of course, you had a dangling pointer somewhere that caused the null terminator to be overwritten.

    Most of these will cause a crash, and are therefore easy to find

    A lot can happen between the creation of an uninitialized pointer and a program crashing -- I have seen it happen. A dangling pointer that overwrites an integer that causes a buffer to overflow that causes another pointer to become invalid, and then the program crashes. Now your debugging process has to reverse that -- except the first thing you are dealing with is trying to figure out why a properly initialized pointer is pointing to an invalid address. It can take hours of work to find the original dangling pointer -- hours that should have been spent on more productive pursuits.

    these are the kind of mistakes that an experienced C programmer very rarely makes

    1. Those mistakes are still made by people with experience
    2. Inexperienced programmers are still out there writing code, and experienced programmers are often required to work with them
    3. Malicious programmers might try to hide these "mistakes," but craft them in a way that causes a backdoor; see e.g. the Underhanded C Coding Contest.

    The learning curve on C (or C++) is the real problem, as there is a period in every programmers career where they are new.

    All languages have a learning curve. The problem is that inexperienced C programmers do not just get "stuck" trying to figure out how to write the code they want to write (which is what the learning curve should be about); they often do not get "stuck" and wind up writing something that passes all test cases but still has some stupid pointer problem. The learning curve in C and C++ is almost as much about learning how to do what you want as it is learning about things you should never do but which are allowed by the language.

  9. Re:One good reason... on What's To Love About C? · · Score: 1

    Exceptions are an essential part in any object oriented programming language. They are the only way to signal that an interaction has left an object in an invalid state.

    Or you could return an error code from member functions, and test that -- which is what the iostream classes in C++ did before exceptions were added to the standard.

    It's native code. If you want to see the stack, either attach a program to a running process or read the core dump

    Except that once an exception is caught, the core dump will have no information about the stack at the point where the exception was thrown, since the stack is unwound by that point. If you want core dumps that include that information, you basically need to replace throw with abort(), or you need a function that causes the core to be dumped without killing the process (perhaps you can point to one).

    Also, exceptions in native code can have information about the stack -- if the stack is not unwound before the exception is caught. If you wanted a core dump with that stack included, you could call abort at that point; you could also resume execution from where the exception was thrown e.g. if you do not want to halt a critical function because of a non-critical error.

    If a destructor signals an error condition, then the object is in an invalid state

    Not true; the exception may have nothing to with the object being destroyed. Suppose, for example, that you have a class that encapsulates your logging process, and the destructor for that class closes the log file. Suppose some function creates a local object of that type, and then tries to write to a separate output file; the write may fail because you ran out of disk space, and that should cause an exception to be thrown. However, if that exception is not caught in the function that created the logging object, then the attempt to close the log file might also fail -- since it might try to flush the stream, which will fail because there is no disk space left. That too should cause an exception to be thrown, but that is not a result of the logging object being in an invalid state (there is nothing that the class could do to prevent the error).

    It is not always the case that you want to abort execution in that case; at the very least, you might want to send a message about running out disk space before exiting, or perhaps continue executing but stop writing to disk.

    so the entire program is in an invalid state

    No, the program is in an error state, which may first require the program to enter additional error states before execution can be resumed. Calling abort() is supposed to be an absolute last resort, reserved for situations where the program has no way to continue executing correctly. Exceptions are supposed to prevent programs from entering invalid states by forcing programs to enter error states; that is why exceptions as an idea are generally good. The problem is that in C++ (and to be fair, several other languages), the transition to the error state can trigger additional errors, which makes the correct error state ambiguous.

    Which is why, again, the correct way to do this is for the exception handler to be located before the stack is unwound. That guarantees that the transition to the error state is completed before anything else happens, which leaves no ambiguity in which error state is entered. It also allows you to return to the next instruction following the throw, which is probably what you want if the exception is of low priority (e.g. maybe you just issue a warning to the user and give them the choice to continue or not).

  10. Re:One good reason... on What's To Love About C? · · Score: 1

    C++ is a static language, but you can still accomplish what you wish by defining functions for the derive types, using RTTI to perform the type comparison, and dynamic_cast to the the derived types. If you meant something else, be precise.

    That is as bad as doing OOP in C -- sure, you can implement OOP constructs, but it adds complexity and it adds new ways for things to crash. C++ only supports single dispatch; you are on your own if you want something more.

    That's a pure lambda, no allocation or deallocation of anything whatsoever. If you meant something else, be precise.

    I was precise: the allocation and deallocation of the lexical environment for a closure. If you return a lambda from a function and your capture specification is simply [&]. then any reference to local variables from the enclosing function in the lambda expression will be invalid. You have to capture by value i.e. with the capture specification [=], which makes a copy, but now you must ensure that you do not free your pointers until you are done using the closure.

    Because exceptions are supposed to signal error conditions in which an object finds itself in an invalid state, and if they aren't handled it means the exception wasn't expected thus the entire program is in an invalid state.

    Except that you could have a top level catch(...) block, and still wind up have your program abort when an exception is thrown. That happens if an exception propagates out of a destructor that was called as part of the stack unwinding process for another exception. This is not a problem in Lisp, since the exception handler is found before the stack unwinding process begins, which allows the stack unwinding to be optional -- a program could resume execution from the site where an exception is thrown (e.g. if you have a non-critical logging function, you probably do not want it to prevent a critical function from running).

    The C++ answer is to simply never allow exceptions to propagate out of destructors. If a destructor cannot finish for some reason, you need another way to indicate that.

    It can't as far as the standard is concerned

    No, reaching the end of a non-void function is not forbidden by the standard, it is left up to implementors to decide what to do. Some compilers will not allow it, some will warn, and some will just ignore it and compile your program without complaint.

    You didn't mention any actual problems except for the last one which is not a feature of the language.

    No, I was just not clear enough about the problems for you to understand what I meant; now that I have clarified, we can try this again.

  11. Re:Control on What's To Love About C? · · Score: 1
    Actually, you should have tried the last one with several compilers and several architectures (yes, GCC will produce some different on x86 than PowerPC). The point of the last one is that the order in which arguments to a function are evaluated is undefined in C, so if you have arguments that have side effects (e.g. function calls that modify global variables), the behavior of your program is undefined:

    http://www.eetimes.com/discussion/programming-pointers/4023961/Evaluating-Function-Arguments

    This can cause all sorts of issues:

    int i = 0;
    int f(){i++; if(i == 1) return 5; else return 6;}

    int g() {i++; return 6;}

    float h(int i, int j){return 1/(i-j);}

    int main()
    {
    h(f(),g());
    return 0;
    }

    The code in main() looks innocent at first -- but it has undefined behavior. This is something that is easy to accidentally write, too; you could call any number of standard library functions that update some global state. The C standard, section 6.5.2.2, says the following about this sort of thing:

    The order of evaluation of the function designator, the actual arguments, and subexpressions within the actual arguments is unspecified, but there is a sequence point before the actual call.

    Things are even worse in C++; you might not even realize that you have function calls in your argument list in C++ e.g. you might be calling a copy constructor for a smart pointer.

  12. Re:because - on What's To Love About C? · · Score: -1

    In other words, the animosity comes from those who aren't qualified to judge.

    I started out programming in C, quickly moved to C++, and eventually grew up and realized that both languages are terrible. I have written C and C++ programs for half my life (and I am in grad school, and I assure you that I am not a prodigy). You have a large class of undefined statements (including some that have no reason to be allowed), bizarre subtleties (like the difference between foo() and foo(void)), "features" that have led to some of the most notorious vulnerabilities and malware in the history of computing (C strings), and a lack of useful features that have been available in other languages for decades (lexical closures come to mind, which only became part of the C++ standard a year ago and are still not part of C).

    C rose to popularity at a time when compiler techniques, garbage collection, and programming language design were all poorly developed and mysterious fields. The fact that C became popular at a time when building a computer out of TTL chips was not extraordinary is testament to that. We are clinging to C because we are terrified of having to rewrite the massive amounts of C code that are out there, but by this point we have probably spent more time debugging things that should not need to be debugged than it would have taken to just rewrite everything.

    Yes, I know, high level languages are not as efficient. Except that they can be as efficient as C, and it is easier to get your code right in modern languages. At this point, debugging should be about identifying logic errors, not trying to find uninitialized pointers or buffer overflows.

  13. Re:Maybe because it compiles down to the metal... on What's To Love About C? · · Score: 1

    But with good coding standards it's a trivial problem with modern tools.

    ...and yet there are memory leaks in the JVM and CLR.

    See, if what you are saying is true about memory leaks in C, then either the people working on the JVM and CLR are incompetent or else there is more to this story than you are letting on. I am inclined to think it is the latter, that there is more to stopping memory leaks than coding standards.

    In other words, if you are right, shouldn't that mean that writing one good runtime for Java or C# or Lisp would solve this problem once and for all?

  14. Re:One good reason... on What's To Love About C? · · Score: 1

    I didn't claim there weren't flaws with the system. If you need to deal with errors in fclose(), then call .close() on your class, before it gets destructed, or pass in the class at a higher level.

    Except that the only safe way to do that is to never have a local object value, smart pointers included. You cannot insert calls to a specific member function into the stack unwinding process, so calling .close() is irrelevant -- the destructor for the class will be called, and if the destructor propagates an exception, the program will halt.

    Basically, if you have a relatively rare case when exceptions don't fit the bill, then you can opt not to use them and revert to manual error checking.

    Except that it is not a "relatively rare" case. Basically, if you ever create a local object value, you have to hope that no exceptions can propagate out of the object's destructor. Maybe you consider local objects to be "relatively rare," but remember that smart pointers, proxy objects, etc. all count (and those are common patterns). Either you forbid exceptions or you mandate that all destructors catch all exceptions, and neither case adequately handles errors (the former is better).

    No, you can't: you missed the point of what I was saying. The optimizer mushes things up so much that even without exceptions, the stack is almost certainly not what it looks like in your program. The current instruction pointer may very well not meaningfully correspond to a line of source code.

    So what? The stack is intact and it can be examined; if you have a bug that is triggered by the changes that the optimizer makes to the stack, that is extremely useful in figuring out what is happening. You missed my point; it is not that you can have a "stack trace" as you might in Java, it is that you can see the actual state of the stack between where the exception was thrown and where it was caught. How is that information not useful to have -- what if you have an uninitialized pointer, and the state of the stack left resulting from an optimization is what is triggering the exception?

    How is destroying the entire stack before the exception can be handled helpful?

  15. Re:but anti trust comes into view with signed code on FSF Criticises Ubuntu For Dropping Grub 2 For Secure Boot · · Score: 1

    See, this is why the corporate overloads invented the term "Reasonable And Non-Discriminatory" (RAND). It is an antitrust violation if your competitors have no way to install their software; it is not a violation if you provide a "RAND" path to do so, like charging $100 for a signing key. Even more so when you can provide real justification for the system -- which in this case is "security from malware!" and in a few years "security from pirates!"

  16. Re:One good reason... on What's To Love About C? · · Score: 2

    Most of the time they ensure that resources are freed when an error occurs

    Except that freeing resources might cause an exception to be thrown (see, for example, the fclose manpage; note the number of errors that fclose might return, and now imagine exceptions being thrown for those errors), which is seemingly innocent (after all, it is an error case) but which is practically guaranteed to create a double exception fault if any destructor fails to catch all exceptions.

    Organisations that ban exceptions are frankly being silly.

    Not really; I have heard several software engineering researchers say that exceptions are the completely wrong approach for high-integrity software, and that the right approach is to use return values and to mandate that something be done with the return value. Even without the problems with C++ exceptions, the entire design of exceptions is problematic -- your program basically branches to some totally unknown place, and the only way to figure out where that place is is to actually throw an exception (compare with overridden methods in objects, where you can determine what method will be invoked by checking the object type). I personally disagree and think that exceptions are a more pragmatic way to handle things, since we cannot all sit down and write proofs of correctness for all of our code, but these guys are experts in software engineering, so their opinion is at least worth considering.

    The trouble is that keeping the stack in tact would seriously hobble the optimizer. After a few passes of inlining, code deduplication, loop unswitching, common subexpression elimination and scheduling, there is, at best a rather tenuous relationship between the generated code and what shape you think the stack ought to have.

    Which is solved by resolving the exception handler before destroying the stack. When that happens, I can just examine the stack exactly as it was when the exception was thrown. It also allows the exception handler to make a decision about whether or not the stack should be destroyed i.e. allowing the program to continue execution from the point where the exception was thrown.

  17. Re:One good reason... on What's To Love About C? · · Score: 1

    It sucks, but less so than all other competing languages that are popular among schools

    FTFY. In my opinion, Common Lisp is a competing language (both are multi-paradigm, both can be compiled directly to machine code, both have seen plenty of use in large projects, etc.) but it beats the pants of C++. How do you get multiple dispatch in C++? Why do you have to manage the allocation and deallocation of the lexical environment when you create a closure in C++11? Why can exceptions cause programs to abort in C++? Why can a C++ function claim to return something, but never actually have a return statement (which can cause things to become completely unpredictable in the case where you were supposed to return an object value where the destructor is virtual)? None of these things are problems in Common Lisp.

  18. Re:Control on What's To Love About C? · · Score: 1

    C does exactly what you tell it to do

    Assuming you even know what you are telling it to do:

    int i = 0;
    int x = i++ * i++;

    char * s = "hello, world!";
    s[0] = 'H';

    int i = 0;
    printf("%d %d %d\n", i++, i++, i++);

    ...and so forth. These, of course, are just toy examples that nobody should encounter in their code, but there an endless number of ways to accidentally write a complicated version of one of these.

  19. Re:Control on What's To Love About C? · · Score: 1

    If you can't handle having that much control (over memory, how the CPU acts on that memory, etc) then your software will have many problems

    Really? Sometimes, I am more worried about getting my logic right than about managing pointers. When I am looking through my code and trying to figure out why the results were not what I expected, I do not want to waste my time trying to decide if the problem was my algorithm or some dangling pointer somewhere. Sometimes, it is nice to know that the problem is with the algorithm and not just that a variable was uninitialized.

    Yes, sometimes you need low-level control. I would venture a guess that the bulk of the code in use today does not fall into that category.

  20. Re:One good reason... on What's To Love About C? · · Score: 3, Interesting

    what features you could therefore remove without detracting from the effectiveness of the language.

    Exceptions -- I personally like exceptions, but exceptions in C++ are terrible and not necessary. C++ exceptions have the following problems:

    1. No clearly defined exception type -- you could potentially catch something that has no descriptive string, no information about the stack, etc.
    2. Double exception faults. This is a subtle problem but one that seems to be easier to trigger in C++ than other languages, and one that could be fixed by changing how exceptions are handled. For those who are not familiar, exceptions that propagate out of a destructor cause abort() to be called if the destructor was called as part of the stack unwinding process for another exception. If I wanted to call abort() when errors occurred, I would not bother with throw statements, I would just call abort().

      Incidentally, the problem here is the order in which things happen. Exceptions should be caught before the stack unwinding process, which guarantees that the handler will execute before another exception is called. This also allows for "restarts" i.e. the ability to resume execution from the point where an exception was thrown, which might make sense (e.g. for exceptions that indicate some non-critical function could not be performed, which should not prevent a critical function from completing).

    I have also seen quite a few large C++ projects that simply ban exceptions, because they wind up creating more problems than they solve.

  21. Re:Maybe because it compiles down to the metal... on What's To Love About C? · · Score: 1

    I have a hard time thinking of good reasons to write drivers in higher level or interpeted languages

    To make auditing easier. Even if you are certain that some function does what it is supposed to do, how do you know there is not a dangling pointer somewhere that will overwrite some code in that function? How do you know that the array bound you received as input is valid, or that the null terminator for your string was even set? C is a pain to audit and debug because of the wild things that can happen with pointers -- having the right logic and having a working program in C are almost orthogonal.

  22. Thanks /. on What's To Love About C? · · Score: 1

    Well, I was going to try to prove my point by giving an example disassembly from SBCL, to show that a high level language with modern features can be compiled to bare metal, but the lameness filter stopped me. Still, Lisp has been compiled to metal for a long time, and it is not nearly as painful as using Pascal, Fortran, or C (unless you really hate parens).

  23. Re:Servers and Laptops on FSF Criticises Ubuntu For Dropping Grub 2 For Secure Boot · · Score: 4, Interesting

    SecureBoot is not a DRM system (for now).

    For now indeed -- it is blindingly obvious that this is a temporary situation.

    If SecureBoot is on, the requirement is that the code executed before ExitBootServices() has to be signed

    Thus closing the one remaining loophole in PC DRM, the loophole that has been the bane of entertainment and software companies (and especially the combination of those, video game companies) for decades. If the bootloader must be signed, then the bootloader can be designed to only load a signed kernel, which will only run signed applications, which will not receive signatures if they can possibly circumvent a DRM system. That is the point here -- you will not be able to just patch software to remove license checks, you will not be able to cheat in video games by executing code in kernel mode (yes, really, people do this -- in MMORPGs, where cheating successfully can yield real world profits), you will not be able to examine memory from processes that forbid it (so no more grabbing secret keys out of RAM), etc. The only reason that has not happened yet is that the PC software ecosystem is so massively complex and there is so much legacy code that no longer has anyone maintaining it, all of which has to be run somehow. I suspect that Microsoft's solution to that will be to create a secure sandbox where unsigned code can be run, but where it is unable to interact with any other software (so e.g. unsigned code could open some process' memory and examine it, but only if that process is running in the sandbox -- and of course, a signed application could forbid being run in a sandbox). They cannot do everyone at once -- gradually moving in for the kill is a better tactic for them.

    So for example one can create a Boot Loader like EFILinux that will be signed and conform to the specification, and that can load unsigned kernels, and those unsigned kernels can contain any code

    Sure, but look at the Fedora rationale; they noted that if they sign code that can be used to launch "malware" that attacks Windows, they will get in trouble. That's the difficulty here -- for a system to be secure in the restricted boot / DRM sense, in must never allow unsigned code to run, except in a strictly confined environment (so certainly not in kernel mode). For now, you can load an unsigned kernel, but the noose is already around your neck -- if you get caught doing something Microsoft (or whoever else) doesn't like, you are in trouble.

  24. Re:With all due respect on FSF Criticises Ubuntu For Dropping Grub 2 For Secure Boot · · Score: 3, Interesting

    You don't have to rely on Canonical unless you want to use their product, which is essentially what choosing software is, you use someone's software (maybe your own) over someone else's because of the choices they made.

    Sure, that's the way things work right now. When UEFI restrictions come into play, things start to work differently. I can choose not to use Ubuntu and Fedora, and then what? I get stuck jumping through hoops just to install anything else -- and while I have the technical expertise and patience needed to do so, it is still annoying, and for some people it is either too annoying or too difficult to do.

    That is the choice this situation forces you into: either you accept the code written by Fedora or Ubuntu, or you have to work hard to get something else up and running / pay for the right to do so. You are not able to simply reject those distros whose choices you disagree with; you must decide if those accepting those choices would be as bad as trying to get something else to work. A few months ago, I stopped using Fedora because of a disagreement I had with their choices (completely unrelated to the boot process); now I have to reevaluate that, because getting the distros I like to run on the next laptop I buy might require more of a time commitment than I can make.

    I honestly don't understand how you have a problem with the concept of distros deciding to do certain things certain ways? Did you write your own package manager and kernel? In which case why are you using Ubuntu anyway? Why are you even using Linux, they've made all sorts of choices for you.

    I am free to accept or reject the choices that other people made. I can always fork a project if I do not like the direction it is taking. Except, of course, if I need a digital signature from the project in order to run my fork on my own computer / if I have to get some company's permission (i.e. by paying a fee).

    It is not about other people making decisions; it is about my freedom to accept those decisions. Maybe I like everything in Ubuntu, except for the bootloader -- maybe I really want to run grub2. Now I am stuck jumping through all sorts of hoops to get that to work -- either buying a key and agreeing to contracts, or putting the system in custom mode and instructing anyone who wants to use my code to do the same. Forking a distro in this model sounds like a giant pain, with extra hurdles and hoops that just push people to use the handful of distros that can pay to play.

  25. Re:I suppose the ultimate solution is... on FSF Criticises Ubuntu For Dropping Grub 2 For Secure Boot · · Score: 1

    Indeed, although we can at least find computers from major manufacturers that will run GNU/Linux -- and we can tell people what to avoid. With Microsoft going full-steam on restricted boot environments, it will only be a few years before we cannot buy a laptop from Dell that will run GNU/Linux (except for those distros that have made a deal with Microsoft -- so much for choice).