http://qs1969.pair.com?node_id=11148274


in reply to Re: Perl XS binding to a struct with an array of chars*
in thread Perl XS binding to a struct with an array of chars*

Dear Rob,

Thank you so much for your hint with Inline:C. I will try this at the next opportunity

I don't know why, but I solved my problem with the following:

_new(class,count, val_arr) char *class int count AV *val_arr PREINIT: EdjeMessageStringSet *message; int index; SV *tmp; char *string; STRLEN len; CODE: Newx(message,1,EdjeMessageStringSet); Renewc(message,count+2, char*,EdjeMessageStringSet); if (message == NULL) croak("Failed to allocate memory in _new function\n"); message->count = count+1; for (index = 0; index <= count; index++) { tmp = *av_fetch(val_arr,index,0); string = SvPVutf8(tmp,len); message->str[index] = savepvn(string,len); } RETVAL = message; OUTPUT: RETVAL

I have to allocate memory for the Edje_Message_String_Set struct and for the array of strings, because it is only at runtime visible, how many strings are in the array. In C this goes through malloc(sizeof(Edje_Message_String_Set) + count * sizeof(char*)). In Perl this works with the Renewc function. The only thing I don't understand is, why I had to reallocate count+1 items of char*. It should be count+1 :-S (because count starts at 1, and the index of the given Perl array starts at 0). (in a C example there is even malloced count-1 * sizeof(char*)...). But whatever, I am happy that it works now...

Replies are listed 'Best First'.
Re^3: Perl XS binding to a struct with an array of chars*
by Marshall (Canon) on Nov 22, 2022 at 07:28 UTC
    I also don't understand this issue with count. If you have something like this:
    typedef struct { int count; char *str[]; } Edje_Message_String_Set;
    You would call malloc for needed memory thusly:
    malloc( sizeof(Edje_Message_String_Set) + (count-1)*sizeof(char *) );
    The sizeof(Edje_Message_String_Set) includes enough space for one integer and one pointer to char. So that is all you need for count==1. If you need an array of 2 pointers to char, then you have to allocate space for one more char*. One pointer to char is included in the smallest struct that you are able to allocate space for. You put a dimension of [1] on the array of pointers. I am not sure that you need that and a blank dimension (no number) may work? This has nothing to do with whether program indices start at zero or one - this just about how much memory do you need for X number of strings?.

    I don't see who manages the destruction of one of these things? Also who manages the memory for the strings themselves? I guess you are doing a shallow copy instead of a clone. Also, the index "for" loop looks pretty weird to me because it looks like it exceeds allocated memory bounds.

    I am curious - what sort of problem are you trying to solve with your XS code?

    Update:
    This memory allocation code looks completely wrong to me:
    I haven't used these Perl memory allocation functions, but from looking at the doc's...

    Newx(message,1,EdjeMessageStringSet); Renewc(message,count+2, char*,EdjeMessageStringSet); if (message == NULL) croak("Failed to allocate memory in _new function\n");
    Ok, with comments:
    void Newx(void* ptr, int nitems, type) void* safemalloc(size_t size) void* Renewc( void *ptr, int size, type, cast ) void Safefree(void* ptr) Newx(message,1,EdjeMessageStringSet); // allocate space for 1 EdjeMessageStringSet // this is enough for just 1 pointer to char // i.e. ok if count==1 // memory is not initialized. Renewc(message,count+2, char*, EdjeMessageStringSet); // Leak some memory from the heap. // Allocate enough space for count+2 char* // copy contents of memory from previous Newx() operation // to this newly allocated memory // Then throw pointer to this new memory away // For extra confusion, also calls "free" on the original pointer!! if (message == NULL) croak("Failed to allocate memory in _new function\n"); // Of course should have checked this after the Newx().
    Ok, so after this, message is a pointer to memory that has already been freed. Some subsequent malloc() could see this memory reassigned to that request and then you are in real trouble! What is saving the day here is that right after this newly unallocated memory block, there is an allocated memory block. Some stuff got copied into this block, but the address of this block got thrown away. So for at least a short time, you can use more memory at the address of "message".

    One issue here is that we are "cheating" by declaring a type whose size can and does actually change! You can't allocate memory for this thing using a method that expects to allocate X number of Y things. So, something like this is needed:

    EdjeMessageStringSet* m = (EdjeMessageStringSet*) safemalloc( sizeof(E +dje_Message_String_Set) + (count-1)*sizeof(char *) ); if (m == NULL) croak("Failed to allocate memory in _new function\n");
      "Renewc" The XSUB-writer's interface to the C "realloc" function, with cast. Memory obtained by this should ONLY be freed with "Safefree". void Renewc(void* ptr, int nitems, type, cast)

      So actually not how you commented it. The Renewc function modifies the pointer rather than returning anything.

      Basically the OP's new version of the code works because sizeof(int) < 2*sizeof(char*)

      The renewc is unneeded though, it could just be

      message= (EdjeMessageStringSet*) safemalloc( sizeof(EdjeMessageStringSet) + (count-1)*sizeof(char*) )

      For this code, the Newx type casting & sizing behavior is simply the wrong API to try to use.

        Yeah, I remember seeing two different I/F definitions and being confused about which one was "truth". My C environment is a bit boogered at the moment so I wasn't able to run test code.

        But in any event, yes, safemalloc() with an explicit size calculation appears to be the correct API in this situation.

Re^3: Perl XS binding to a struct with an array of chars*
by MaxPerl (Acolyte) on Nov 20, 2022 at 16:58 UTC

    sorry, some typos: "The only thing I don't understand is, why I had to reallocate count+2 items of char*. It should be count-1 :-S (because count starts at 1, and the index of the given Perl array starts at 0)..."

      MaxPerl, I haven't managed to get my head around the exact requirements of the XS code, so I can't comment much more.
      I noticed in your original post that you had a DESTROY function, and I just wanted to add that I couldn't see any evidence that it will be called automagically.
      You may find that you have to explicitly call DESTROY (or Safefree) in your code, in order to avoid memory leaks.

      Cheers,
      Rob