alloca() is hard...


  • Discourse touched me in a no-no place

    @gwowen That's bad, but the nasty here is that the struct is actually:

    struct foo {
        int member[16];
    }
    

    and hence the & is implicit.



  • @greybeard said in alloca() is hard...:

    someobject.methodreturningstring.c_str()

    The problem there is that the temporary variable is just the pointer, not the thing pointed to. So only the pointer variable - not the pointed to char's - gets their life extended.



  • @dkf said in alloca() is hard...:

    and hence the & is implicit.

    Ugh. That is nasty.

    I hate hate hate hate hate that array-args-turn-into-pointers rule of C. Now I have a new reason to. It's the worst. (Well, OK in C there are a lot of good candidates for "worst", but its up there).


  • Discourse touched me in a no-no place

    @gwowen said in alloca() is hard...:

    It's the worst.

    But is it the @mikeTheLiar?

    C is unashamedly a language that sits just above machine code. As such, it is quite annoying to use in places and you need to be very aware of what is going on. (Of course, you can go even lower level than C, but the annoyance level goes up fast. C is a hell of a lot nicer than Assembly.) I'd definitely encourage people to use higher level languages than C where possible, but “where possible” is an important caveat.



  • @dkf said in alloca() is hard...:

    C is unashamedly a language that sits just above machine code

    I like C. I use it nearly every day. If I didn't find it useful, I wouldn't loathe and detest the bits of it that are annoying, like the stupid rule about pointer decay. I understand why it exists as an historical anomaly (no-one wanted to pass large arrays by value back in the day for efficiency reasons), but it really puts arrays outside the normal type system.
    Case in point:

    typedef char[16] my_array_t;

    int main()
    {
    my_array_t var;
    printf("%u\n",(unsigned) sizeof(my_array_t)); // prints 16
    printf("%u\n",(unsigned) sizeof(var)); // prints 16
    }

    void fun(my_array_t arg)
    {
    printf("%u\n",(unsigned) sizeof(my_array_t)); // prints 16
    printf("%u\n",(unsigned) sizeof(arg)); // prints size of a pointer
    }

    I'd have like to have seen it slowly deprecated in C99 and removed in C11, giving you 12 years to go and put all those '&' into your codebase.



  • Decided to do some actual tests of the matter under discussion. gcc 4.8.5.

    test.c:

    struct foo {
        int member;
        int array_member[10];
    };
    struct foo struct_func();
    
    extern f1(int arg);
    extern f2(int* arg);
    
    int main() {
        f1(struct_func().member); // This is fine
        f2(&struct_func().member); // Bad bad bad
        f2(struct_func().array_member);
    }
    

    make test.o:

    cc    -c -o test.o test.c
    test.c: In function ‘main’:
    test.c:12:8: error: lvalue required as unary ‘&’ operand
         f2(&struct_func().member); // Bad bad bad
            ^
    test.c:13:5: error: invalid use of non-lvalue array
         f2(struct_func().array_member); // Also bad
         ^
    make: *** [test.o] Error 1
    

    Both problematic invocations actually fail to compile with reasonably obvious error messages. Of course, embedded compilers are notorious for having old versions and worse behaviour than modern x86 compilers.



  • @gwowen said in alloca() is hard...:

    and I missed where you were passing the pointer to the member, rather than the member, so sorry.

    It was because the member was an array, so C automatically turns it into a pointer



  • @wharrgarbl said in alloca() is hard...:

    f(function_returning_a_struct().arraymember)
    Is not. In your example the entire struct is copied to f() parameter, and in mine f() receive a pointer to a member of the struct, that is destroyed when f() is called.

    I don't see any pointers, to either structs or members of structs, or anything that's destroyed in your f().

    @pleegwat said in alloca() is hard...:

    While f(function_returning_a_struct().array_member) copies a pointer to part of the return value

    Nope - still no pointers.

    Ok - new example; one that compiles and behaves more or less as expected:

    pjh@hpdesktop:/tmp$ cat -n structs.c
         1  #include <stdio.h>
         2
         3  struct foo {
         4          int bar;
         5          char baz[5];
         6          char qux[10000];
         7  };
         8
         9
        10  struct foo foo_as_struct(void){
        11          struct foo f = { 42, "asdf" };
        12          return f;
        13  }
        14
        15  struct foo* foo_as_pointer_to_static(void){
        16          static struct foo f = { 43, "qwer" };
        17          return &f;
        18  }
        19
        20  struct foo* foo_as_pointer_to_auto(void){
        21          struct foo f = { 44, "zxcv" };
        22          return &f;
        23  }
        24
        25  int main(void){
        26          printf("%d, %s\n", foo_as_struct().bar, foo_as_struct().baz); 
        27          printf("%d, %s\n", foo_as_pointer_to_static()->bar, foo_as_pointer_to_static()->baz);
        28          printf("%d, %s\n", foo_as_pointer_to_auto()->bar, foo_as_pointer_to_auto()->baz); // UB
        29
        30          return 0;
        31  }
    pjh@hpdesktop:/tmp$ make -B structs
    cc     structs.c   -o structs
    structs.c: In function ‘foo_as_pointer_to_auto’:
    structs.c:22:9: warning: function returns address of local variable [-Wreturn-local-addr]
      return &f;
             ^~
    pjh@hpdesktop:/tmp$ ./structs 
    42, asdf
    43, qwer
    Segmentation fault (core dumped)
    pjh@hpdesktop:/tmp$ 
    


  • @gwowen said in alloca() is hard...:

    @cvi Correct. It also works if somemethod() takes a const reference, due to the C++ rule extending the lifetime of the const references to temporaries.

    Pretty sure you're confused about that. First, in that example somemethod() takes a pointer to a (const) char (since that's what c_str() normally returns). It can also take a const ref to a char-pointer, that doesn't change anything.

    In C++, the lifetime of the temporary is guaranteed to be for the full expression, so whatever the type of the argument of somemethod() is doesn't matter.

    The lifetime extension of const references comes into play in the following:

    struct X { void f(); ... };
    X make_X();
    
    //...
    X const& xref = make_X();
    
    //...
    xref.f(); // still valid, the const-ref extended the life of the returned temporary.
    


  • @pjh said in alloca() is hard...:

    I don't see any pointers

    Arrays are passed as pointers



  • @cvi said in alloca() is hard...:

    Pretty sure you're confused about that. First, in that example somemethod() takes a pointer to a (const) char (since that's what c_str() normally returns). It can also take a const ref to a char-pointer, that doesn't change anything.

    Nah, I just explained myself poorly. Assuming

    fun1(const mytype * t);
    fun2(const mytype& t);

    mytype func();

    Now I can do
    fun1(&func()); // UB, as in C.
    fun2(func()); // OK, because const references are magic

    which both do essentially the same thing - because pointers and references are very similar under the hood.

    The big difference is when I call fun2() the value returned from func() is guaranteed to still be around (its lifetime is extended because I took a reference to it).

    In fun1() the return value will get destroyed, and the only object that lasts till the end of the full expression is the pointer-to-a-destroyed mytype.



  • #include <stdio.h>

    struct mytype
    {
    int arr[16];
    };

    void fun(const int* t)
    {

    }

    struct mytype get()
    {
    struct mytype retval;
    return retval;
    }

    int main()
    {
    fun(get().arr);
    }

    In gcc this doesn't compile - "Invalid use of non-lvalue array". Which is nice.



  • For me the biggest WTF about alloca() is Microsoft's "safe" version:

    _alloca: This function is deprecated because a more secure version is available; see _malloca.

    Nothing too WTF-y so far...

    Unlike _alloca, which does not require or permit a call to free to free the memory so allocated, _malloca requires the use of _freea to free memory. In debug mode, _malloca always allocates memory from the heap.

    Congratulations Microsoft, you've missed the point of alloca() entirely!
    That said, that point was mostly valid in C (since C++ has destructors) and back then Microsoft might have had already taken the decision of leaving C devs to rot.



  • @gwowen said in alloca() is hard...:

    In fun1() the return value will get destroyed, and the only object that lasts till the end of the full expression is the pointer-to-a-destroyed mytype.

    No, the return value won't get destroyed immediately. See the link on lifetimes I posted earlier. The return value is guaranteed to be alive for the full expression. You can even try it out:

    struct X { ~X() { printf( "X::~X\n" ); } };
    
    X make_X() { return X{}; }
    void don_t_take_x( int ) { printf( "dont()\n" ); }
    
    int main()
    {
        don_t_take_x( (make_X(), 0) );
        return 0;
    }
    

    The instance of X isn't bound to a reference in the above example, it's in fact completely discarded. Yet, the standard guarantees that it isn't destroyed until after dont_t_take_x() returns (and thus the full expression is finished).

    Now I can do
    fun1(&func()); // UB, as in C.

    GCC/Clang will error out at that:
    error: taking address of temporary [-fpermissive]
    MSVC has a sneaky non-standard extension which makes the above legal (you need /W4 to get a warning about it).

    You can however do the following (which is also legal because temporary lifetimes last the full expression):

    mytype const* take_addr( mytype const& mt ) { return &mt; }
    
    fun1( take_addr(func()) );
    


  • @medinoc Interesting. Thanks.



  • @pjh said in alloca() is hard...:

    alloca

    Talking about this, I think I've recently seen a kernel bug regarding libraries like glibc uses alloca() to allocate large block of memory (like 64KB) for it's internal functions, and can easily allocate across stack guard page, so they have in increase the guard page size.





  • @gwowen psst -- if you put ``` on its own line above and below your code block, it'll format as a code block with syntax highlighting. You can also tell it what language by providing a hint on the top ``` -- e.g. ```c++.



  • @cheong said in alloca() is hard...:

    @pjh said in alloca() is hard...:

    alloca

    Talking about this

    I'm in the process of turning off _CRT_SECURE_NO_WARNINGS(etc). And fixing other various security things. And removing _alloca() because someone didn't want to deal with memory cleanup in C++. :headdesk:



  • @gwowen also, I should've mentioned that you can click :fa_ellipsis_v: next to the voting buttons on a post and click the "View raw" option; that'll show the markdown that the poster used. If you see anything in a post that you're not sure how they did it, you can use it to find out.


Log in to reply
 

Looks like your connection to What the Daily WTF? was lost, please wait while we try to reconnect.