2

I'm trying to write a function taking as a parameter a buffer (void *), the type size, the type name and the number of elements. The buffer may contain values of a limited number of basic types (int, float, double, ...). Within this function, I would like to be able to, let's say, increment each value. It looks like that :

void increment (void *buffer, int type_size, char *type_name, int n_elements)
{
    for (int i=0;  i<n_elements; i++)
        ((MACRO(type_name))(buffer))[i]++; // ???

    return;
}   

My question is as simple as: how can I "dynamically" cast my buffer? I can add more function parameters if needed. Maybe something can be done using macros but I can't figure this out.

  • 3
    You can't unambiguously deduce the type from its size. So some other indication of type is needed. – Eugene Sh. Aug 02 '18 at 15:56
  • 1
    You should not do something like that. – Stargateur Aug 02 '18 at 15:56
  • 1
    You need some kind of coding (0 is int, 1 is float, 2 is double, etc. etc.), and a (big) case switch. Also, this only works if the original void buffer is exactly of the type you are converting it back to, otherwise you step into undefined behaviour, and you are also at risk of memory alignment problems. If you really have to do something like that, which would be made through a template in C++, you are better off with a macro. – Antonio Aug 02 '18 at 15:57
  • @EugeneSh. If needed, I can give more parameters to my function. – François T. Aug 02 '18 at 16:00
  • If you can convert the type_name to an enum, you can at least write a switch instead of a chain of `if(!strcmp(type_name, "int")) { ... } else if (!strcmp(...` – Useless Aug 02 '18 at 16:07
  • You cant :). Not in C – 0___________ Aug 02 '18 at 16:12
  • It seems you want this solved at runtime, right? Then you can't use macros. – Support Ukraine Aug 02 '18 at 16:15
  • Hmm... As the expected number of types is limited, a switch is probably the best option. Sometimes, simpler is better. – François T. Aug 02 '18 at 16:15
  • You don't want to do this. It's likely to be very detrimental to performance because it can't be optimized. Any good optimizing compiler will be able to optimize entire loops - but the expansion inside your loop likely can't be effectively optimized, making this a **S-L-O-W** way to increment a potentially large range of an array. – Andrew Henle Aug 02 '18 at 16:16
  • BTW: What is the use of `type_size` ? – Support Ukraine Aug 02 '18 at 16:18
  • _sigh_ "generic something in C" nonsense, the 7712312316th. – too honest for this site Aug 02 '18 at 16:26
  • Seems like a buffer of "variant" types. You can't do it without additional info describing the type of each element. – Hernán Aug 02 '18 at 16:27
  • 2
    You need to show a use case of this function. I think this is a XY problem: https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem – Support Ukraine Aug 02 '18 at 16:46
  • @4386427 I tried to answer Y indeed, but maybe I answered Z :-) – Antonio Aug 02 '18 at 18:20
  • @Antonio I think you are right :-) – Support Ukraine Aug 02 '18 at 19:42
  • @4386427 Am I right about Y, or am I right about Z? :D :D :D – Antonio Aug 03 '18 at 16:48
  • @Antonio Z I think :D – Support Ukraine Aug 03 '18 at 22:20
  • I'd suggest looking into the implementation of existing serialization/deserialization libraries -- protobuf, msgpack, etc; take note of which of them transmit type metadata in-band, or out-of-band and transmit identifiers to versioned type structures. – Charles Duffy Aug 06 '18 at 14:49

3 Answers3

1

If you are dealing in a finite number of specific types for this to work with, you could write a separate function for each type. If you don't want to have to pick the right function for each type, you can use C11's _Generic to select based on the type. As a basic example:

#include <stdio.h>
#include <stdlib.h>

void increment_int(int *, int);
void increment_long_long_int(long long int *, int);

#define increment(buffer, n_elements) _Generic((buffer), \
                int *:  increment_int, \
      long long int *:  increment_long_long_int \
      )(buffer, n_elements)

void increment_long_long_int (long long int *buffer, int n_elements)
{
    for (int i=0;  i<n_elements; i++)
    {
        buffer[i]++;
    }
    return;
}   

void increment_int (int *buffer, int n_elements)
{
    for (int i=0;  i<n_elements; i++)
    {
        buffer[i]++;
    }
    return;
}

int main(void) {
    int buff[20] = {0};
    long long int buff2[20] = {0};
    increment(buff, 20);
    increment(buff2, 20);
    printf("%d\n", buff[5]);
    printf("%lld\n", buff2[8]);
    return EXIT_SUCCESS;
}
Christian Gibbons
  • 4,272
  • 1
  • 16
  • 29
0

Use a macro instead:

INCREMENT (buffer, n_elements) \
{\
    for (typeof(n_elements) i=0;  i < n_elements; i++)\
        (buffer)[i]++;\
}

or better (works also for floating point types):

INCREMENT (buffer, n_elements) \
{\
    for (typeof(n_elements) i=0;  i < n_elements; i++)\
        (buffer)[i] += 1;\
}

The typeof(n_elements)(*) is to avoid signed/unsigned comparison problems. The parenthesis around buffer are to prevent problem in case you invoke your macro with, for example, INCREMENT (buffer + 4, 12).

On the calling side it would look something like this:

double buffer[100];
//[...] Initialize here your buffer
INCREMENT (buffer, 100); //Semicolon is optional, but I prefer having it

or

#define BUFFER_SIZE 200
unsigned int* p = malloc(sizeof(*p) * BUFFER_SIZE);
//[...] Initialize here your buffer
INCREMENT (p, BUFFER_SIZE); //Semicolon is optional, but I prefer having it


(*) typeof is actually non standard, a workaround is given by _Generic in C11, details available here.
Antonio
  • 19,451
  • 13
  • 99
  • 197
  • I don't see how this can work - there's no known type in the code posted - the conversion appears to be by casting, so the target type has to be known in advance, rendering `typeof()` moot. – Andrew Henle Aug 02 '18 at 16:19
  • @AndrewHenle I assume the intent is to ditch the function and use the macro instead, and the `void *` becomes unnecessary if not writing a function. – Christian Gibbons Aug 02 '18 at 16:21
  • I think he wants function, as the size of the type is unknown compile type. So Macros will not do. – 0___________ Aug 02 '18 at 17:38
  • @AndrewHenle This is not intended to solve the question in the title, but to offer an alternative for the problem shown in the text, namely having *something* that is able to perform a given operation on a buffer of any data type. I am updating by adding how it would look on the calling side. – Antonio Aug 02 '18 at 18:02
  • Please note that typeof is not standard C and is mostly obsolete since C11 _Generic. – Lundin Aug 06 '18 at 14:30
  • @Lundin Thanks for the heads up, I updated the answer, I hope in a satisfactory way. – Antonio Aug 06 '18 at 14:43
-2

You can only have something silly like this (gcc version):

#include <stdint.h>

void incrementAlmostAnySize(void *ptr, size_t size, size_t element, int sign)
{
    union 
    {
       uint8_t  u8; 
       uint16_t u16;
       uint32_t u32;
       uint64_t u64;
       int8_t  i8; 
       int16_t i16;
       int32_t i32;
       int64_t i64;
    }*uptr = ptr + size * element;   //non gcc : (void *)((uint8_t *)ptr + size * element); 
    if(sign)
    {
        switch(size)
        {
            case 1:
                uptr -> i8++;
                break;
            case 2:
                uptr -> i16++;
                break;
            case 4:
                uptr -> i32++;
                break;
            case 8:
                uptr -> i64++;
                break;
            default:
                break;
        }
    }
    else
    {
                switch(size)
        {
            case 1:
                uptr -> u8++;
                break;
            case 2:
                uptr -> u16++;
                break;
            case 4:
                uptr -> u32++;
                break;
            case 8:
                uptr -> u64++;
                break;
            default:
                break;
        }

    }
}

usage:

char x[1000];

void foo()
{
    incrementAlmostAnySize(x, sizeof(long long), 10, 1);
    incrementAlmostAnySize(x, sizeof(uint32_t), 2, 0);

    for(size_t i = 0; i < sizeof(x) / sizeof(uint64_t); i ++)
        incrementAlmostAnySize(x, sizeof(uint64_t), i, 0);
}
0___________
  • 60,014
  • 4
  • 34
  • 74