TIL that in C++, you can declare variable in if statement


  • Banned

    @dkf said:

    Yes, but anything not in your stack frame/activation record can be smashed when an interrupt happens (e.g., at the end of your timeslice).

    Correct me if I'm wrong, but doesn't x86 calling convention work like that part of callee's stack frame is created by the caller - specifically the part with arguments?

    @Steve_The_Cynic said:

    I think you've fundamentally misunderstood how functions return values on x86. The amount of stack groping A() and B() would have to do to allow this is unrealistic, if you mean "takes no parameters" in an absolute sense (i.e. not even implicit compiler-generated ones).

    Of course I meant parameters in the "data to operate on" sense!

    @Steve_The_Cynic said:

    If there's an implicit parameter that shows where to put the return value (there are scenarios where this will be the case, like if A() or B() returns a large struct), and that points to what will be the "argument" part of the activation record for the call to C(), then yes, there's no need to copy, but you can do that on a no-machine-stack architecture as well.

    Except on "machine-stack" architecture, this implicit parameter can be stack pointer itself.

    @Steve_The_Cynic said:

    I'd love to know where you're getting all the straw for this conversation, and why you aren't worried about the fire risk.

    Some men just want to watch the world burn.


  • Discourse touched me in a no-no place

    @Gaska said:

    Correct me if I'm wrong, but doesn't x86 calling convention work like that part of callee's stack frame is created by the caller - specifically the part with arguments?

    Yes, but you can't count on the argument space being big enough to take a returned structure. The safe approach is to have the caller allocate the space for the return value prior to doing the call. That's easy to make guaranteed thread- and exception-safe.


  • Banned

    @dkf said:

    Yes, but you can't count on the argument space being big enough to take a returned structure.

    I don't see a problem. In fact, I don't see a relation between argument space and returned struct.

    Let's say 20 bytes must be passed to function C and it returns 30 bytes. I make the input in my stack frame, then call C. C needs 50 bytes on stack for all its data (including arguments passed to it), so it increments stack pointer by 30. It prepares the returned struct and when it's done, it decrements stack pointer by 20 and returns to the caller. I don't see any potential problem related to threading or exceptions here - at any point in time, all data I use at the moment are all in my stack frame.



  • You are either an epic-scale troll, or you really don't have a clue how native-compiled programming languages work, possibly both.

    If some function f() is going to return a 30 byte struct then the caller must allocate somewhere a 30-byte space (common alignment restrictions suggest it will in fact be 32 bytes, but that's another story) and pass the address of this space to f() as a hidden argument ("C" family language term, Pascal-family languages call it an "actual parameter"). Because the target struct could be anywhere in memory (on the heap, in a local stack-based variable, in a "static" local variable, etc.), using the stack pointer as the hidden parameter will not suffice. f() will allocate for itself whatever space it needs for its local variables by whatever mechanism is applicable to the calling conventions and hardware architecture. When f() executes a return statement (e.g. return s;), it will copy its local variable s to the location given in the hidden argument, and then clean up the stack normally as if it were a function returning void.

    In other words, it behaves as if:
    [code]struct foo f(int x, char y)
    {
    struct foo s;

    s.the_x = x;
    s.the_y = y;

    return s;
    }
    [/code]

    was really this:
    [code]void f(int x, char y, struct foo *__return_pointer)
    {
    struct foo s;

    s.the_x = x;
    s.the_y = y;

    *__return_pointer = s;
    return;
    }
    [/code]

    The parameter __return_pointer is required because the caller could do any of these, and it must work:
    [code] /* file scope / global scope variable */
    struct foo global;

    /* function-scope "static" variable */
    static struct foo static_local;

    /* function-scope "auto" variable */
    struct foo auto_local;

    /* heap variable */
    struct foo *pHeap = malloc(sizeof(struct foo));

    global = f(5,'3');
    static_local = f(6,'F');
    auto_local = f(99,'0');
    *pHeap = f(123,'&');
    [/code]

    Or in the second version of f():
    [code] /* file scope / global scope variable */
    struct foo global;

    /* function-scope "static" variable */
    static struct foo static_local;

    /* function-scope "auto" variable */
    struct foo auto_local;

    /* heap variable */
    struct foo *pHeap = malloc(sizeof(struct foo));

    f(5,'3',&global);
    f(6,'F',&static_local);
    f(99,'0',&auto_local);
    f(123,'&',&*pHeap);
    [/code]

    The fundamental concept here is no different in operation in the two versions of f(), and the stack cleanup is obviously different in fine-grained detail between e.g. x86 and System/370, but there is no real difference in broad-strokes concepts.

    And you're quite right, there is no problem related to threading and exceptions (i.e. hardware interrupts, page faults, etc. C++-style exceptions are a different animal) in what you describe. The only real problem with how you describe it is this (the description here is based on x86 and the actions you described, and does not reflect reality):

    The caller stacks the overt arguments then stacks the return address, then transfers control to f(). Let's say that SP is 0x104000 after the return address was stacked (so the return address is located at address 0x104000), and that it is x86 where the stack grows downwards. (Machine-stack CPUs that have up-growing stacks are very rare, and x86 is not one of them.)

    f() makes space according to the correctly-oriented version of the stack growth by subtracting ... what was it you said? 30 bytes ... so now SP is 0x103FE2. f() does its work, and then goes to return the struct. It sets up the struct, and adds 20 to SP, so SP becomes 0x103FF6. Now we retrieve the return address from 0x103FF6 and return ... somewhere. That somewhere is not where we came from because that address is stored at 0x104000.

    No, I'm right. You have no clue what goes on inside native-compiled languages. Are you a troll? I've given up trying to tell whether people are trolls.


  • Banned

    @Steve_The_Cynic said:

    You are either an epic-scale troll, or you really don't have a clue how native-compiled programming languages work, possibly both.

    I'm flattered.

    @Steve_The_Cynic said:

    If some function f() is going to return a 30 byte struct then the caller must allocate somewhere a 30-byte space (...) and pass the address of this space to f() as a hidden argument

    That, or it might be function f() that allocates the struct and then returns a pointer to it to the caller. And if it's callee who allocates, it doesn't have to be hidden argument - it might as well be explicit, because why not?

    @Steve_The_Cynic said:

    Because the target struct could be anywhere in memory

    It can be anywhere only if you don't constrain it by API contract.

    @Steve_The_Cynic said:

    using the stack pointer as the hidden parameter will not suffice

    Unless you always want the struct to be created in stack memory. In this case, since no one will ever want to get it somewhere else, the stack pointer itself will be fully sufficient.

    @Steve_The_Cynic said:

    When f() executes a return statement (e.g. return s;), it will copy its local variable s to the location given in the hidden argument, and then clean up the stack normally as if it were a function returning void.

    That's not the only option. Especially with return value optimization and copy ellision, which were designed specifically to avoid redundant copies of return values.

    More after break.



  • @Gaska said:

    @Steve_The_Cynic said:
    If some function f() is going to return a 30 byte struct then the caller must allocate somewhere a 30-byte space (...) and pass the address of this space to f() as a hidden argument

    That, or it might be function f() that allocates the struct and then returns a pointer to it to the caller. And if it's callee who allocates, it doesn't have to be hidden argument - it might as well be explicit, because why not?

    Where do you propose that f() allocates the struct, and how do you propose to handle that memory being exhausted? And please don't say "on the stack", because that is really, really stupid.

    @Gaska said:

    @Steve_The_Cynic said:
    Because the target struct could be anywhere in memory

    It can be anywhere only if you don't constrain it by API contract.

    Calling convention, not API, and why would you want to do that, especially as the language does not impose such a constraint?

    @Gaska said:

    @Steve_The_Cynic said:
    using the stack pointer as the hidden parameter will not suffice

    Unless you always want the struct to be created in stack memory. In this case, since no one will ever want to get it somewhere else, the stack pointer itself will be fully sufficient.

    Why would you want to impose such a restriction on the use of a calling convention?

    @Gaska said:

    @Steve_The_Cynic said:
    When f() executes a return statement (e.g. return s;), it will copy its local variable s to the location given in the hidden argument, and then clean up the stack normally as if it were a function returning void.

    That's not the only option. Especially with return value optimization and copy ellision, which were designed specifically to avoid redundant copies of return values.

    More after break.


    The return value optimisation / copy elision are applicable to C++, to avoid the extra copy/destruct cycle when someone says:
    [code]
    class SomeClass;

    SomeClass f(/* some parameters */)
    {
    // some code

    return SomeClass(/* some constructor parameters */);
    }
    [/code]

    To be strictly compliant with the historical interpretation of the language, you have to construct a temporary in space provided by the caller, and then the caller must copy-construct or assign that into the destination variable, then destruct the temporary. The copy elision / return value optimisation allow the caller to provide the target variable rather than a temporary. However, in the case of:
    [code]
    class SomeClass;

    SomeClass f(/* some parameters */)
    {
    // some code

    return SomeClass(/* some constructor arguments */);
    }

    void someOtherFunction(/* some other parameters */)
    {
    SomeClass obj(/*constructor arguments */);

    // some code

    obj = f( /* some arguments */ );

    // some more code
    }
    [/code]

    The assignment must remain an assignment, so copy elision / return value optimisation are not applicable, or at least not for obj.

    If you want to bamboozle me with technical jargon, you'll have to try harder.



  • To the general audience: I hope you're all enjoying the show...


  • Considered Harmful

    Oh yes. Carry on.


  • Banned

    @Steve_The_Cynic said:

    Where do you propose that f() allocates the struct

    Stack.

    @Steve_The_Cynic said:

    and how do you propose to handle that memory being exhausted?

    Segmentation fault.

    @Steve_The_Cynic said:

    And please don't say "on the stack", because that is really, really stupid.

    On x86, it's not. Unless there's an actual reason why it's impossible (and if there is, it would be nice if you could share it with me).

    @Steve_The_Cynic said:

    Calling convention, not API

    The only difference between a calling convention and an API contract is abstraction level. I have a problem that I often confuse many thing with each other if they're the same.

    @Steve_The_Cynic said:

    Why would you want to impose such a restriction on the use of a calling convention?

    Performance?

    @Steve_The_Cynic said:

    The return value optimisation / copy elision are applicable to C++

    And every other language that have value semantics for objects. C, for example.

    @Steve_The_Cynic said:

    The assignment must remain an assignment, so copy elision / return value optimisation are not applicable, or at least not for obj.

    It is applicable if SomeClass is POD.

    @Steve_The_Cynic said:

    If you want to bamboozle me with technical jargon, you'll have to try harder.

    I don't want to bamboozle you with technical jargon. I would actually love if we dropped all jargon altogether and you answered me why do you insist on copying things around if there is a way to avoid it. And if there is no way to avoid it, please point out where exactly I'm wrong with my thinking that randomly messing with stack pointer is perfectly safe as long as my data is always in range.



  • @Gaska said:

    @Steve_The_Cynic said:
    And please don't say "on the stack", because that is really, really stupid.

    On x86, it's not. Unless there's an actual reason why it's impossible (and if there is, it would be nice if you could share it with me).

    I didn't say it is impossible. I said it is stupid. We'll see why lower down.

    @Gaska said:

    @Steve_The_Cynic said:
    Calling convention, not API

    The only difference between a calling convention and an API contract is abstraction level. I have a problem that I often confuse many thing with each other if they're the same.

    If you had said "ABI" and not "API", I'd have let you have it. (It isn't strictly correct, but it's close enough.) But there's a hell of a difference between a collection of functions that you can call, and what they promise to do, and fine-grained details of how a function is called.

    @Gaska said:

    @Steve_The_Cynic said:
    Why would you want to impose such a restriction on the use of a calling convention?

    Performance?

    So you want to impose an arbitrary language-incompatible restriction on how functions are called in order to make questionable improvements in the performance of a relatively rare case?

    That's why your proposal is stupid. Of course it is possible, but it is stupid.

    @Gaska said:

    @Steve_The_Cynic said:
    The return value optimisation / copy elision are applicable to C++

    And every other language that have value semantics for objects. C, for example.

    Hmm. OK. I've only ever heard it discussed in the context of C++. In C, for example, it doesn't matter, from a semantic point of view, whether you do it or not. In C++, however, doing the optimisation is pedantically speaking incorrect, or at the very least you can write code that detects if the compiler does it or not.

    @Gaska said:

    @Steve_The_Cynic said:
    The assignment must remain an assignment, so copy elision / return value optimisation are not applicable, or at least not for obj.

    It is applicable if SomeClass is POD.

    It's arguable that if it is declared as a class and not a struct, it is not a POD type, but if we say instead what you actually mean here, that it has vacuous destruction and copy-construction semantics (i.e. the copy constructor can be implemented as a simple memcpy just like for struct assignment/initialisation, and the destructor is empty), then yes, you are right. In the real world, copy constructors of classes are not simple memcpy, and destructors of classes are not empty.

    @Gaska said:

    @Steve_The_Cynic said:
    If you want to bamboozle me with technical jargon, you'll have to try harder.

    I don't want to bamboozle you with technical jargon. I would actually love if we dropped all jargon altogether and you answered me why do you insist on copying things around if there is a way to avoid it. And if there is no way to avoid it, please point out where exactly I'm wrong with my thinking that randomly messing with stack pointer is perfectly safe as long as my data is always in range.

    You introduced the straw man of surplus copies, not me. If you go back and read carefully what I wrote(1), I explained how the question of whether we copy or not is entirely independent of the architecture.

    (1) I advise you to actually do this. It will be the first time you have read carefully what I wrote.

    And while I refuse to allow "perfectly safe" (the sensitivity of such manipulations to subtle or not-so-subtle compiler bugs in an unusually destructive manner says it isn't perfectly safe), "safe" is a reasonable claim, but because it is possible to avoid groping the stack like that without damaging performance, we should avoid doing so. Also, "randomly messing" is never safe. "Carefully manipulating" is feasible but unnecessary and dangerous.

    I'm left with the feeling that you have never looked at the assembly-level code generated by a compiler. I have, and I've seen some weird things, like the compiler(2) that generated "load 1 into AX, compare AX to 0, jump if they aren't equal" in its most aggressive optimisation mode for a "while(1) { do stuff }" loop; or the gymnastics that were necessary on an MC68HC11 to access variables on the (machine) stack. (It had no stack-pointer-relative addressing modes.)

    (2) We replaced it, duh.



  • @Gaska said:

    I don't want to bamboozle you with technical jargon. I would actually love if we dropped all jargon altogether and you answered me why do you insist on copying things around if there is a way to avoid it.

    Oh, and one other small point... What you are proposing might improve performance (it's debatable) in the case of a function returning a struct by value when that function is called as the argument to a function taking the same type of struct by value, but it will disimprove performance in just about all other cases.



  • @Gaska said:

    @Steve_The_Cynic said:
    When f() executes a return statement (e.g. return s;), it will copy its local variable s to the location given in the hidden argument, and then clean up the stack normally as if it were a function returning void.

    That's not the only option. Especially with return value optimization and copy ellision, which were designed specifically to avoid redundant copies of return values.

    More after break.


    Argh. I'm guilty of not reading your stuff carefully enough. Sorry.

    What I described doesn't have any redundant copies, so you introduced some noise to imply that that it does and therefore I'm stupid or something.

    There's a word for people who do that, but it isn't very polite.

    (Well, there's all manner of different words for such people, but none of them are polite.)

    For the "obj = f(stuff)" case in C (or in C++ with POD types), the caller will provide the address of obj, and f() will copy directly to it. In this trivial case, the compiler might optimise the local struct away, but in more complex cases where there are data dependencies leading f() to return one of two different structs or stuff like that, no, it won't be able to. But if there is a surplus copy, it will be in f() because of an inadequate optimiser, not in the caller.

    In C++, copy elision refers to the fact that in this C++ example:
    [code]class SomeClass;
    SomeClass f(/* some parameters /)
    {
    // some code
    return SomeClass(/
    some constructor arguments /);
    }
    void someOtherFunction(/
    some other parameters */)
    {
    SomeClass obj( f(/some arguments/) );
    // some more code
    }
    [/code]

    f() is able to construct the return value directly into someOtherFunction()'s variable obj.

    In the case where there is a non-vacuous assignment into an existing variable from the return value of f(), a temporary is necessary.


  • Java Dev

    Also here are some moore major downsides to the return on the stack approach:

    • You cannot have the arguments and the return value available at the same time. If you do need that, you'll need to copy stuff. This complicates the compiler's job.
    • The position of the caller's local variables relative to the stack pointer is not constant. If the callee takes 30 bytes of arguments and returns a 20 byte struct, the stack pointer has moved by 10 bytes between the two, and the caller needs strict separation between pre-call and post-call code, as the same variables live at different offsets.


  • There was once a man with eyes like sexy dinner plates, approximately 8 inches accross (I measured, with my massive wang) who saw the discourse and injected it with his salty surprise. From this germinated a legend, proud and mighty, and from the tip of the legend ejaculated a message, a message of the sexiest proportions.

    Then there was a forest of dicks, in which Hanzo hid from the baddies or something. People didn't see him because reasons, but he was there, being useless at everything.

    Now I kneel here, mouth open wide, ready to recharge my salty surprise supply. I will consume your strength and add it to my own. give me your strength mortal, for I am the mighty jizz demon of the north, and the north remembers how to take a load.


  • Banned

    @Steve_The_Cynic said:

    But there's a hell of a difference between a collection of functions that you can call, and what they promise to do, and fine-grained details of how a function is called.

    Example of API contract: the value under this reference passed as an argument will not be modified. The returned in will be in range (0, 9999). This function must be called after that function. Such and such exceptions can occur. You must provide pointer to such big array in this parameter.

    Example of calling convention: this and this register will be preserved. Alignment of stack pointer must be that much bytes. You must do this and this before making function call. Return address is in this place.

    As I said, there is no difference except the abstraction level. Both have many fine-grained details, except the definition of "fine" changes.

    @Steve_The_Cynic said:

    So you want to impose an arbitrary language-incompatible restriction on how functions are called in order to make questionable improvements in the performance of a relatively rare case?

    1. I don't make architecture for compilers - I make compilers for architecture.
    2. This is common case, not rare case.
    3. It doesn't restrict the expressiveness of the high-level language in any way before compilation.
    4. This would be enforced in opt-in way only for functions that need it.

    @Steve_The_Cynic said:

    That's why your proposal is stupid. Of course it is possible, but it is stupid.

    The speedup might be non-trivial. If something is stupid but works, it isn't stupid.

    @Steve_The_Cynic said:

    It's arguable that if it is declared as a class and not a struct, it is not a POD type

    Fun fact: class and struct are synonyms in C++, except for default visibility of members which doesn't at all affect generated code. In particular, it doesn't affect whether a class is POD or not.

    @Steve_The_Cynic said:

    In the realJavaish world of OOP, copy constructors of classes are not simple memcpy, and destructors of classes are not empty.

    FTFY. Remember that C++ is also used for systems programming and gamedev, and both of these groups hate OOP. In fact, its often a point of honor to make as many objects PODs as possible - it has serious (and very measurable) benefits.

    @Steve_The_Cynic said:

    You introduced the straw man of surplus copies, not me.

    Yes, because I wanted to know how it's done on zSystem (typo?).

    @Steve_The_Cynic said:

    And while I refuse to allow "perfectly safe" (the sensitivity of such manipulations to subtle or not-so-subtle compiler bugs in an unusually destructive manner says it isn't perfectly safe)

    We aren't talking about buggy compilers - we are talking about technical possibility of such solution. At least I do. I realize my claim might have been too bold, but saying that A is wrong because we have fifty year history of Foos implementing As wrong is hardly a counterargument for anything (except maybe enabling compiler optimizations).

    @Steve_The_Cynic said:

    Also, "randomly messing" is never safe.

    Never say never.

    @Steve_The_Cynic said:

    "Carefully manipulating" is feasible but unnecessary and dangerous.

    Careful manipulation might be unnecessary, but it's never dangerous. Uncareful manipulation is.

    @Steve_The_Cynic said:

    I'm left with the feeling that you have never looked at the assembly-level code generated by a compiler.

    I did it enough times to know I don't want to do it ever again. Most of my knowledge comes from gcc manpages and random blogs.


  • Winner of the 2016 Presidential Election Banned

    @TwelveBaud well, I see what you mean.

    Sorry, algorythmics, I already have a boyfriend.



  • @Gaska said:

    @Steve_The_Cynic said:
    But there's a hell of a difference between a collection of functions that you can call, and what they promise to do, and fine-grained details of how a function is called.

    Example of API contract: the value under this reference passed as an argument will not be modified. The returned in will be in range (0, 9999). This function must be called after that function. Such and such exceptions can occur. You must provide pointer to such big array in this parameter.

    Example of calling convention: this and this register will be preserved. Alignment of stack pointer must be that much bytes. You must do this and this before making function call. Return address is in this place.

    As I said, there is no difference except the abstraction level. Both have many fine-grained details, except the definition of "fine" changes.


    OK, this sounds like a tomayto/tomahto thing. Forget it.

    @Gaska said:

    @Steve_The_Cynic said:
    So you want to impose an arbitrary language-incompatible restriction on how functions are called in order to make questionable improvements in the performance of a relatively rare case?

    1. I don't make architecture for compilers - I make compilers for architecture.
    2. This is common case, not rare case.
    3. It doesn't restrict the expressiveness of the high-level language in any way before compilation.
    4. This would be enforced in opt-in way only for functions that need it.
    • And so what? I don't do either of those things, but I apparently have a better grasp of the latter than you do.
    • People often return large structures by value from functions and pass the return value directly to other functions by value? No, I don't think so, mostly because the performance issues are (perceived to be) significant.
    • Um, yes, or at least it makes it more expensive than it should be to call the inner function in any other context.
    • That's all very well, but no function needs it, not even to gain a performance advantage, because the other way (you know, the one I suggested that works properly whether or not there is a machine stack, and doesn't care where the destination struct is) doesn't do any surplus copies. And your method needs to copy stuff around in order to make room on the stack...

    @Gaska said:

    @Steve_The_Cynic said:
    That's why your proposal is stupid. Of course it is possible, but it is stupid.

    The speedup might be non-trivial. If something is stupid but works, it isn't stupid.

    It won't even be as much as trivial, compared to doing it the right way.

    In fact, that's why it is stupid, because it makes things worse (or can't be used) in all the other cases, makes the compiler's code generator - and the generated code - more fragile, and doesn't improve things at all in the one case where you say it is (OK, "might be") useful.

    @Gaska said:

    @Steve_The_Cynic said:
    It's arguable that if it is declared as a class and not a struct, it is not a POD type

    Fun fact: class and struct are synonyms in C++, except for default visibility of members which doesn't at all affect generated code. In particular, it doesn't affect whether a class is POD or not.

    @Steve_The_Cynic said:

    In the realJavaish world of OOP, copy constructors of classes are not simple memcpy, and destructors of classes are not empty.

    FTFY. Remember that C++ is also used for systems programming and gamedev, and both of these groups hate OOP. In fact, its often a point of honor to make as many objects PODs as possible - it has serious (and very measurable) benefits.

    I dismissed PODs as irrelevant. I was talking about non-vacuous destructor, copy constructor, and assignment semantics, like cleaning up POD-format allocations (i.e. memory allocation stored in raw pointers, file handles, etc.). As soon as you introduce these, you start having to accommodate the difference between simply overwriting an object and asking it to modify itself.

    And you live in a weird world, one where there are "objects" that don't have any code attached. If it doesn't have code attached, it isn't an object except in the colloquial sense. If it is an object, it has code attached, so it is not a POD, although in general, we only care about it when the attached code is the automatic construct/destruct/assign functions. This is a discussion of a programming language topic, so discussing terminology in the colloquial sense of words is likely to cause ... issues.

    @Gaska said:

    @Steve_The_Cynic said:
    You introduced the straw man of surplus copies, not me.

    Yes, because I wanted to know how it's done on zSystem (typo?).

    Do you normally find out how things are done by proposing straw men? That's a weird world you live in. You could have just asked how it handles that case, perhaps mentioning that you didn't see how it could be done without some extra copies.

    @Gaska said:

    @Steve_The_Cynic said:
    And while I refuse to allow "perfectly safe" (the sensitivity of such manipulations to subtle or not-so-subtle compiler bugs in an unusually destructive manner says it isn't perfectly safe)

    We aren't talking about buggy compilers - we are talking about technical possibility of such solution. At least I do. I realize my claim might have been too bold, but saying that A is wrong because we have fifty year history of Foos implementing As wrong is hardly a counterargument for anything (except maybe enabling compiler optimizations).

    If you introduce a code generation option that requires intricate(1) correctness inside the compiler, you must consider what happens if it is buggy, because the history of computing is littered with compilers that were notorious for bugs, like one version of Turbo C that I used that sometimes generated incorrect segment overrides; and the optimise-for-speed options in Visual C++ 5, which were considered more dangerous than playing Russian Roulette with a fully-loaded revolver.

    (1) More intricate than normal, that is.

    @Gaska said:

    @Steve_The_Cynic said:
    Also, "randomly messing" is never safe.

    Never say never.

    You might get away with randomly messing with things, but it isn't safe.

    @Gaska said:

    @Steve_The_Cynic said:
    "Carefully manipulating" is feasible but unnecessary and dangerous.

    Careful manipulation might be unnecessary, but it's never dangerous. Uncareful manipulation is.

    It's dangerous because you might carefully manipulate it in a way that is wrong, which I suppose is just saying that the bar of required caution is very high. That in turn says it isn't a job for people, because people who are always 'on' in that respect are very, very uncommon.

    @Gaska said:

    µ@Steve_The_Cynic said:
    I'm left with the feeling that you have never looked at the assembly-level code generated by a compiler.

    I did it enough times to know I don't want to do it ever again. Most of my knowledge comes from gcc manpages and random blogs.

    The gcc manpages are incomplete. Heck, they even say so. The info pages are more likely (less unlikely?) to be useful. And "random blogs" as a source of anything but a quick laugh? 'Nuff said.


  • Discourse touched me in a no-no place

    @Steve_The_Cynic said:

    If it is an object, it has code attached

    This is your opinion.


  • Banned

    @Steve_The_Cynic said:

    And so what?

    And so, language-incompatibility is moot point.

    @Steve_The_Cynic said:

    People often return large structures by value from functions and pass the return value directly to other functions by value?

    Depends on how large. It's not uncommon to do this with 4x4 float matrices.

    @Steve_The_Cynic said:

    No, I don't think so, mostly because the performance issues are (perceived to be) significant.

    Additional pointer jump might impose significant performance overhead too.

    @Steve_The_Cynic said:

    Um, yes

    How is the expressiveness of high-level language restricted before compilation, exactly? Or do you think I'm arguing that structs should always be returned on stack and passed by value? No, I don't - pointers work as well, and it's per-function choice whether to use values directly or pass pointers around.

    @Steve_The_Cynic said:

    or at least it makes it more expensive than it should be to call the inner function in any other context.

    Again, depends.

    @Steve_The_Cynic said:

    That's all very well, but no function needs it

    Technically speaking, no function needs arguments at all - you could theoretically generate separate function for each set of input. But it would be impractical. Avoiding copying data around is very practical, though.

    @Steve_The_Cynic said:

    not even to gain a performance advantage, because the other way (you know, the one I suggested that works properly whether or not there is a machine stack, and doesn't care where the destination struct is) doesn't do any surplus copies.

    But, pointer jump!

    @Steve_The_Cynic said:

    And your method needs to copy stuff around in order to make room on the stack...

    Depends on how good the compiler is at arranging data on stack.

    @Steve_The_Cynic said:

    It won't even be as much as trivial, compared to doing it the right way.

    Oh, I just realized why you're so stubborn in this discussion - because you don't even let into your head the thought that, maybe, possibly, I might have some point, or be even partially correct!

    @Steve_The_Cynic said:

    I dismissed PODs as irrelevant.

    And that's your problem. They are very relevant if we're talking about performance and optimization.

    @Steve_The_Cynic said:

    And you live in a weird world, one where there are "objects" that don't have any code attached.

    Because they don't. Object is a block of memory. Functions operate on blocks of memory. Any semantical meaning is lost after compilation (except for ABI contracts vel calling conventions), or exists solely in people's heads.

    Of course, when thinking in C++ instead of assembly, I have all my objects with their respective methods, inheritance hierarchies, strong typing etc. etc.

    @Steve_The_Cynic said:

    If it doesn't have code attached, it isn't an object except in the colloquial sense.

    How much attached must the code be to be attached? Is int abs(int) {...} enough attachment to code to call ints objects? Would it change anything if my abs() function was extension method instead of free function?

    @Steve_The_Cynic said:

    You might get away with randomly messing with things, but it isn't safe.

    It is safe if I know what I'm doing. If I took all SP decrements in some binary, randomly added 8, 16, 24, 32 or 40 to the amount SP is decremented by, and fixed the offsets the variables are referred by, nothing bad would happen (assuming I haven't exhausted the stack and no variable needs alignment of 16 or more).

    @Steve_The_Cynic said:

    It's dangerous because you might carefully manipulate it in a way that is wrong

    If I manipulated it in a way that is wrong, I wasn't careful enough.

    @Steve_The_Cynic said:

    which I suppose is just saying that the bar of required caution is very high

    Exactly. But if I have this amount of caution, then I can do what I want and shit will work.

    @Steve_The_Cynic said:

    That in turn says it isn't a job for people, because people who are always 'on' in that respect are very, very uncommon.

    So, you claim it's dangerous not because of any actual reason, but because people are shitty in getting stuff right. Hint: PEBKAC isn't valid argument in theoretical discussions.

    @Steve_The_Cynic said:

    And "random blogs" as a source of anything but a quick laugh? 'Nuff said.

    Better source than a random guy with troll username on troll forum.


  • Discourse touched me in a no-no place

    @Gaska said:

    It is safe if I know what I'm doing. If I took all SP decrements in some binary, randomly added 8, 16, 24, 32 or 40 to the amount SP is decremented by, and fixed the offsets the variables are referred by, nothing bad would happen (assuming I haven't exhausted the stack and no variable needs alignment of 16 or more).

    The main reason for avoiding stack fuckery is that in a real system, you're not the only user of the stack. You don't need to behave right, of course, but if you don't then you shouldn't be surprised when the OS randomly shits all over your data because it decided you'd had enough time slice when you're in the middle of doing something critical. 😄

    Our systems are fundamentally based around doing some things asynchronously. That means there are practices from the past that we can no longer safely use. For example, it used to be the case that function calls that returned structures would use a common shared area for doing that. (I don't remember if it was a single common space for all returning functions or a space per function.) This practice worked enough in single-threaded code, but is desperately unsafe once you go to multi-threaded. We Don't Do That Now™.


  • Banned

    @dkf said:

    The main reason for avoiding stack fuckery is that in a real system, you're not the only user of the stack.

    Actually, yes I am.

    @dkf said:

    you shouldn't be surprised when the OS randomly shits all over your data because it decided you'd had enough time slice when you're in the middle of doing something critical.

    Do you know how task switching works? Because that's not how it works.


  • Discourse touched me in a no-no place

    @Gaska said:

    Because that's not how it works.

    ORLY? What about if a signal handler is called on return to userspace? (Assuming you've not installed a separate stack for that case.) By far the simplest thing is to just use the space in your activation record plus whatever you've been told explicitly about.

    Ugh, it sounds like you're one of these people who insists on living dangerously. We Advise You Not To Do That. Most of the time you get away with it and so you assume that it's actually safe, and then complain bitterly when the shit hits the fan for real. No sympathy…


  • Banned

    @dkf said:

    ORLY? What about if a signal handler is called on return to userspace?

    Signal handlers should do everything to ensure their work doesn't influence the workings of application code. If they flip even a single bit anywhere in the part of stack the app has already allocated without application's consent, the system is totally broken and should have never been shipped to anyone.

    @dkf said:

    Ugh, it sounds like you're one of these people who insists on living dangerously.

    There's nothing dangerous in changing SP as long as it stays between 8 and beginning of stack frame at all times.

    @dkf said:

    We Advise You Not To Do That.

    And I Appreciate That And Also Seriously Do You Really Think I Am Kind Of Guy That Would Hand Write Even A Single Line Of Assembly Code? All The Time I'm Talking About What The Compilers Are Theoretically Able To Do.



  • @Gaska said:

    @Steve_The_Cynic said:
    And so what?

    And so, language-incompatibility is moot point.

    Explain, please, why and/or how that relates to the place the quotation comes from.

    @Gaska said:

    @Steve_The_Cynic said:
    People often return large structures by value from functions and pass the return value directly to other functions by value?

    Depends on how large. It's not uncommon to do this with 4x4 float matrices.

    Really? You copy the whole float matrix to the stack? You don't pass it by address/reference?

    @Gaska said:

    @Steve_The_Cynic said:
    No, I don't think so, mostly because the performance issues are (perceived to be) significant.

    Additional pointer jump might impose significant performance overhead too.

    Explain? Where did this "pointer jump", whatever that is, come from?

    Come to think of it, what is an "additional pointer jump"? I'm thinking it's just another straw man.

    @Gaska said:

    @Steve_The_Cynic said:
    Um, yes

    How is the expressiveness of high-level language restricted before compilation, exactly? Or do you think I'm arguing that structs should always be returned on stack and passed by value? No, I don't - pointers work as well, and it's per-function choice whether to use values directly or pass pointers around.

    You are, indeed, trying to derail people's brains on this one. No, I never thought you were arguing that at all. All I ever said was that your proposal to, in the specific case, avoid copying that isn't happening anyway, was as a result a stupid idea. That's a little overstated, perhaps, but it is probably purpose-free and certainly benefit-free. Don't you get it? The copies aren't happening anyway, on either architecture, so why are you expending so much effort trying to avoid them?

    @Gaska said:

    @Steve_The_Cynic said:
    or at least it makes it more expensive than it should be to call the inner function in any other context.

    Again, depends.

    No, it adds a copy to get the returned structure out of the magic bit of glued-on stack into the correct location, if the correct location isn't the glued-on stack.

    @Gaska said:

    @Steve_The_Cynic said:
    That's all very well, but no function needs it

    Technically speaking, no function needs arguments at all - you could theoretically generate separate function for each set of input. But it would be impractical. Avoiding copying data around is very practical, though.

    Gaska the Straw Man strikes again! The level of non sequitur in your posts is dangerously high.

    Where did the line about arguments come from?

    And again the bullshit about copying.

    @Gaska said:

    @Steve_The_Cynic said:
    not even to gain a performance advantage, because the other way (you know, the one I suggested that works properly whether or not there is a machine stack, and doesn't care where the destination struct is) doesn't do any surplus copies.

    But, pointer jump!

    But, :wtf: is that?

    @Gaska said:

    @Steve_The_Cynic said:
    And your method needs to copy stuff around in order to make room on the stack...

    Depends on how good the compiler is at arranging data on stack.

    At the very least, you need to move the return address because it is stored (by the call instruction) in part of where the output struct will be (remember, you said that the called function handwaves the stack to make room for the output struct, not the caller), but you also need to do something about the arguments.

    Or you pull it into a register, and jump via that register as the way to return. And then you have a pointer jump!

    @Gaska said:

    @Steve_The_Cynic said:
    It won't even be as much as trivial, compared to doing it the right way.

    Oh, I just realized why you're so stubborn in this discussion - because you don't even let into your head the thought that, maybe, possibly, I might have some point, or be even partially correct!

    That's because I know what I'm talking about.

    @Gaska said:

    @Steve_The_Cynic said:
    I dismissed PODs as irrelevant.

    And that's your problem. They are very relevant if we're talking about performance and optimization.

    Sorry, I meant that they are irrelevant to the discussion of what happens if you have to use destructors / copy constructors / assignment operations, and doing so obstructs copy elision and the like.

    @Gaska said:

    @Steve_The_Cynic said:
    And you live in a weird world, one where there are "objects" that don't have any code attached.

    Because they don't. Object is a block of memory. Functions operate on blocks of memory. Any semantical meaning is lost after compilation (except for ABI contracts vel calling conventions), or exists solely in people's heads.

    Of course, when thinking in C++ instead of assembly, I have all my objects with their respective methods, inheritance hierarchies, strong typing etc. etc.

    Ah, terminological handwaving. Trolling at its best. Grub is lonely.

    @Gaska said:

    @Steve_The_Cynic said:
    If it doesn't have code attached, it isn't an object except in the colloquial sense.

    How much attached must the code be to be attached? Is int abs(int) {...} enough attachment to code to call ints objects? Would it change anything if my abs() function was extension method instead of free function?

    int abs(int) is a free-standing function, so it is not a member of int. If it were int int::abs(), that would be an extension method for objects of type int.

    Do you know anything about anything?

    @Gaska said:

    @Steve_The_Cynic said:
    You might get away with randomly messing with things, but it isn't safe.

    It is safe if I know what I'm doing. If I took all SP decrements in some binary, randomly added 8, 16, 24, 32 or 40 to the amount SP is decremented by, and fixed the offsets the variables are referred by, nothing bad would happen (assuming I haven't exhausted the stack and no variable needs alignment of 16 or more).

    But that's hardly "randomly messing". What you describe here is more like "carefully manipulating".

    @Gaska said:

    @Steve_The_Cynic said:
    It's dangerous because you might carefully manipulate it in a way that is wrong

    If I manipulated it in a way that is wrong, I wasn't careful enough.

    @Steve_The_Cynic said:

    which I suppose is just saying that the bar of required caution is very high

    Exactly. But if I have this amount of caution, then I can do what I want and shit will work.

    Well, gee, reading what I wrote, I'd never have guessed that! That's what "the bar of required caution" usually implies, yes.

    @Gaska said:

    @Steve_The_Cynic said:
    That in turn says it isn't a job for people, because people who are always 'on' in that respect are very, very uncommon.

    So, you claim it's dangerous not because of any actual reason, but because people are shitty in getting stuff right. Hint: PEBKAC isn't valid argument in theoretical discussions.

    You feel driven to post that on this site that's dedicated to the consequences of PEBKAC where the chair is occupied by programmers?

    @Gaska said:

    @Steve_The_Cynic said:
    And "random blogs" as a source of anything but a quick laugh? 'Nuff said.

    Better source than a random guy with troll username on troll forum.

    Excuse me? I am a cynic.



  • Addendum: I may be a cynic, but I'm not a Cynic.


  • Banned

    @Steve_The_Cynic said:

    Explain, please, why and/or how that relates to the place the quotation comes from.

    Look:

    @Gaska said:

    @Steve_The_Cynic said:
    @Gaska said:
    @Steve_The_Cynic said:
    So you want to impose an arbitrary language-incompatible restriction on how functions are called in order to make questionable improvements in the performance of a relatively rare case?

    I don't make architecture for compilers - I make compilers for architecture. explanation: it's not the architecture that should care about compability with pre-existing compilers of high-level languages (pre-existing, because compatibility with non-existing makes no sense), but it's the compilers of high-level languages that should care about compability with pre-existing architectures

    And so what? I don't do either of those things, but I apparently have a better grasp of the latter than you do.

    And so, language-incompatibility is moot point.


    Is it clear to you now?

    @Steve_The_Cynic said:

    Really? You copy the whole float matrix to the stack? You don't pass it by address/reference?

    I default to passing by value, and when I get shitty performance, I switch to heap-allocation. Thanks to inlining, copy ellision and return value optimization, I don't often have to.

    @Steve_The_Cynic said:

    Explain? Where did this "pointer jump", whatever that is, come from?

    Pointer jump comes from the fact you have a pointer to where the object is stored. Whenever you have an address to data instead of data, you must first get this data and only then operate on it - and this might trigger cache miss, which might be very costly. Data on stack is nearly guaranteed to always be in cache.

    @Steve_The_Cynic said:

    Don't you get it? The copies aren't happening anyway, on either architecture, so why are you expending so much effort trying to avoid them?

    You either copy data around between stack frames, hold the structs in heap memory behind a pointer, or mess with stack pointer. First means copying data, second means dynamic allocation and pointer jumps, third is my proposal.

    @Steve_The_Cynic said:

    No, it adds a copy to get the returned structure out of the magic bit of glued-on stack into the correct location, if the correct location isn't the glued-on stack.

    The alternative being dynamic allocation and pointer jumps. As I said, it depends which is more performant in specific case.

    @Steve_The_Cynic said:

    The level of non sequitur in your posts is dangerously high.

    Thankfully you don't read your own posts, or you would die!

    @Steve_The_Cynic said:

    At the very least, you need to move the return address because it is stored (by the call instruction) in part of where the output struct will be (remember, you said that the called function handwaves the stack to make room for the output struct, not the caller), but you also need to do something about the arguments.

    Or the compiler might calculate what goes where in the stack frame, and make sure no data overlaps one another right from the start even deep into execution.

    @Steve_The_Cynic said:

    Sorry, I meant that they are irrelevant to the discussion of what happens if you have to use destructors / copy constructors / assignment operations, and doing so obstructs copy elision and the like.

    I never mentioned constructors/destructors/assignment operators anywhere specifically because they make what I propose to do impossible by definition.

    @Steve_The_Cynic said:

    Ah, terminological handwaving.

    You started this handwaving with your "code attached" mumbo jumbo!

    @Steve_The_Cynic said:

    int abs(int) is a free-standing function, so it is not a member of int. If it were int int::abs(), that would be an extension method for objects of type int.

    Both of these compile to exactly the same code (same opcodes in the same order, same calling convention, same ABI, same result, same usage, same everything except maybe the way the symbol is mangled). Why is this at-this-point-non-existent-difference so important? Even if it is, does it change anything?

    Also, you haven't answered the question whether int is an object or not if int::abs() exist.

    @Steve_The_Cynic said:

    But that's hardly "randomly messing".

    I literally used random function FFS! You can't get more random than that without access violation!

    @Steve_The_Cynic said:

    But that's hardly "randomly messing". What you describe here is more like "carefully manipulating".

    But didn't you say that carefully manipulating is dangerous too?

    @Steve_The_Cynic said:

    Well, gee, reading what I wrote, I'd never have guessed that!

    You surely haven't guessed that careful manipulation implies being careful.

    @Steve_The_Cynic said:

    You feel driven to post that on this site that's dedicated to the consequences of PEBKAC where the chair is occupied by programmers?

    Just because TDWTF is about PEBKAC, not every post here must be about PEBKAC.

    @Steve_The_Cynic said:

    Excuse me? I am a cynic.

    Trolls are often cynics, and cynics are often trolls.



  • @Gaska said:

    @Steve_The_Cynic said:
    Explain, please, why and/or how that relates to the place the quotation comes from.

    Look:

    ((SNIP))

    Is it clear to you now?

    My "and so what?" was directed strictly at your statement "I don't make architecture for compilers - I make compilers for architecture." I even numbered it carefully to link it to your statement. And you decided to attach it to something else.

    @Gaska said:

    @Steve_The_Cynic said:
    Explain? Where did this "pointer jump", whatever that is, come from?

    Pointer jump comes from the fact you have a pointer to where the object is stored. Whenever you have an address to data instead of data, you must first get this data and only then operate on it - and this might trigger cache miss, which might be very costly. Data on stack is nearly guaranteed to always be in cache.

    So "pointer jump" is a term I've never heard before meaning following a pointer. Got it.

    @Gaska said:

    @Steve_The_Cynic said:
    Don't you get it? The copies aren't happening anyway, on either architecture, so why are you expending so much effort trying to avoid them?

    You either copy data around between stack frames, hold the structs in heap memory behind a pointer, or mess with stack pointer. First means copying data, second means dynamic allocation and pointer jumps, third is my proposal.

    It is quite possible to avoid the copy without doing either the second or the third.

    You, as I demonstrated way back, set up a zone of memory where the new struct will end up (in the case of the nested call, this is in the argument block for the outer function call, so it is on the stack), and pass its address to the inner function as a hidden "out" parameter. The inner function will do what it needs and output the struct into the hidden parameter. You write it as if the struct is local and will be copied, and you let the compiler decide whether it needs to do that or whether it can skip the copy by always writing directly through the pointer. Remember: the case where I fill in one struct and return it directly is probably not the interesting one because it can be automatically optimised like this. If the source of the data is a struct selected from a table by the result of some calculation, there's a hard to avoid copy required, in all cases, but there is only one copy required.

    (You avoid that copy by returning a pointer to the struct in the table and passing that pointer to the outer function, of course.)

    @Gaska said:

    @Steve_The_Cynic said:
    No, it adds a copy to get the returned structure out of the magic bit of glued-on stack into the correct location, if the correct location isn't the glued-on stack.

    The alternative being dynamic allocation and pointer jumps. As I said, it depends which is more performant in specific case.

    There is no dynamic allocation required. (And I'd also observe that your allocation method is dynamic as well, unless by dynamic you mean "from the heap".)

    @Gaska said:

    @Steve_The_Cynic said:
    The level of non sequitur in your posts is dangerously high.

    Thankfully you don't read your own posts, or you would die!

    @Steve_The_Cynic said:

    At the very least, you need to move the return address because it is stored (by the call instruction) in part of where the output struct will be (remember, you said that the called function handwaves the stack to make room for the output struct, not the caller), but you also need to do something about the arguments.

    Or the compiler might calculate what goes where in the stack frame, and make sure no data overlaps one another right from the start even deep into execution.

    You said the caller does not do the allocation, so while it knows it will happen, it does nothing to accommodate it. The consequence of that is that the stack on entry will contain the return address then the arguments then the local variables of the caller (assuming this is x86 with a unified "machine" stack). Just before return, the stack must contain the return address then the returned struct then the local variables.

    Just to pre-empt one thing you might say: If the caller carefully crafts the space where the arguments are so that the cleanup isn't necessary, then it did the allocation, not the called function, but your side of this whole discussion is based on the called function groping the stack to make the allocation.

    @Gaska said:

    @Steve_The_Cynic said:
    Ah, terminological handwaving.

    You started this handwaving with your "code attached" mumbo jumbo!

    Normal definition of "object" in OOP, actually.

    @Gaska said:

    @Steve_The_Cynic said:
    int abs(int) is a free-standing function, so it is not a member of int. If it were int int::abs(), that would be an extension method for objects of type int.

    Both of these compile to exactly the same code (same opcodes in the same order, same calling convention, same ABI, same result, same usage, same everything except maybe the way the symbol is mangled). Why is this at-this-point-non-existent-difference so important? Even if it is, does it change anything?

    Also, you haven't answered the question whether int is an object or not if int::abs() exist.

    @Steve_The_Cynic said:

    If it were int int::abs(), that would be an extension method for objects of type int.

    Seems to me I said that. "objects of type int". Geez. If you want to accuse me of not saying something, make sure I really didn't say it.

    @Gaska said:

    @Steve_The_Cynic said:
    But that's hardly "randomly messing".

    I literally used random function FFS! You can't get more random than that without access violation!

    You and I both know that's a really stretched literal interpretation of the words "randomly messing". It's a juvenile and stupid trick. Stop it. We equally know that those words normally suggest "doing stuff without taking proper precautions" or similar.

    @Gaska said:

    @Steve_The_Cynic said:
    But that's hardly "randomly messing". What you describe here is more like "carefully manipulating".

    But didn't you say that carefully manipulating is dangerous too?

    Less dangerous, as I said.

    @Gaska said:

    @Steve_The_Cynic said:
    Well, gee, reading what I wrote, I'd never have guessed that!

    You surely haven't guessed that careful manipulation implies being careful.

    I should have realised you're the sort of idiot who will deliberately misinterpret sarcasm.

    @Gaska said:

    @Steve_The_Cynic said:
    You feel driven to post that on this site that's dedicated to the consequences of PEBKAC where the chair is occupied by programmers?

    Just because TDWTF is about PEBKAC, not every post here must be about PEBKAC.

    @Steve_The_Cynic said:

    Excuse me? I am a cynic.

    Trolls are often cynics, and cynics are often trolls.

    Ah, fuck it. I can't be bothered to argue with you anymore. Bye. Pity this forum doesn't have a killfile feature.


  • Banned

    @Steve_The_Cynic said:

    My "and so what?" was directed strictly at your statement "I don't make architecture for compilers - I make compilers for architecture."

    And my statement about architecture and compilers was directed strictly at the part of your post that says "language-incompatible restrictions".

    @Steve_The_Cynic said:

    I even numbered it carefully to link it to your statement.

    I didn't specify which part exactly I'm talking about in each of my points because I assumed basic reading comprehension skills from the reader. Sorry, my bad.

    @Steve_The_Cynic said:

    And you decided to attach it to something else.

    I attached it to its context. Should I rather attach it to something unrelated? I'm confused.

    @Steve_The_Cynic said:

    So "pointer jump" is a term I've never heard before meaning following a pointer. Got it.

    Sorry. I tend to overuse jargon sometimes, forgetting that not all people have the same background as I.

    @Steve_The_Cynic said:

    It is quite possible to avoid the copy without doing either the second or the third.

    You, as I demonstrated way back, set up a zone of memory where the new struct will end up (in the case of the nested call, this is in the argument block for the outer function call, so it is on the stack), and pass its address to the inner function as a hidden "out" parameter.


    Right, I haven't thought of that. But it might not always be optimal - e.g. allocating 32 bytes and passing 8-byte pointer to it is 25% memory overhead. As always, it depends, and case-by-case profiling should be made if performance is a problem.

    @Steve_The_Cynic said:

    You said the caller does not do the allocation

    Did I? If so, I'm sorry, I didn't mean it. I think I said that the caller should prepare the first part of the stack frame?

    @Steve_The_Cynic said:

    Normal definition of "object" in OOP, actually.

    I'm used to ISO C++ definition of object, which is quite different from how OOP define it.

    @Steve_The_Cynic said:

    Seems to me I said that. "objects of type int". Geez. If you want to accuse me of not saying something, make sure I really didn't say it.

    Okay, this time I really missed the answer. For which I'm sincerely sorry.

    ...After re-reading your post, I'm confident you haven't answered the question "why is this at-this-point-non-existent-difference so important". I'd like to ask it again.

    @Steve_The_Cynic said:

    You and I both know that's a really stretched literal interpretation of the words "randomly messing".

    Well, it seems you stretch your own definition as much as you can for it to include all unsafe and exclude all safe things.

    @Steve_The_Cynic said:

    We equally know that those words normally suggest "doing stuff without taking proper precautions" or similar.

    For me, randomly messing is doing random things. Which means something with unpredictable pattern. You can be both random and follow the rules.

    @Steve_The_Cynic said:

    Less dangerous, as I said.

    But still dangerous. I'd like to know why.

    @Steve_The_Cynic said:

    I should have realised you're the sort of idiot who will deliberately misinterpret sarcasm.

    And vice versa.

    @Steve_The_Cynic said:

    Ah, fuck it. I can't be bothered to argue with you anymore.

    Note to self: never again say that cynics are trolls in Steve's vicinity or he will lose his shit.


Log in to reply