1

I have a C library (I'm using it in C++) that defines a struct and a function to operate on it.

struct s {
  type1 x1;
  type2 x2;
  type3 x3;
  type4 x4;
  type5 x5;
};

void f(s* x);

I know that f doesn't do anything involving s::x4 or s::x5. Since both of them are useless for my purpose, and I have a lot of instances of s, I'd like to allocate them in an array that packs them so that s[n+1] starts right after s[n].x3. Would doing something like the following result in undefined behavior? Assume that x4 and x5 are never used.

struct s_trimmed {
  type1 x1;
  type2 x2;
  type3 x3;
};

size_t num_s = 1000;
char *mem = new char[stuff_before + num_s*sizeof(s_trimmed) + stuff_after];

for (size_t i=0; i<nums; ++i)
  f((s*)(mem + stuff_before + i*sizeof(s_trimmed)));

mem is a char array, because it's just a chunk of memory, and I want other things to be in it besides the instances of s.

SU3
  • 5,064
  • 3
  • 35
  • 66
  • The shown code does not look that bad. Though everything related to `stuff_before` is a little fishy. But you probably are going to use it in parallel with library-implemented functions. Please extend your example accordingly. – Yunnosch May 18 '18 at 04:52
  • 1
    You didn't create any `s`, using the array as such is UB. Will it work? Probably, if the types are all trivial – Passer By May 18 '18 at 05:01
  • @Yunnosch Yes, I'd use it with library-implemented functions. `f` is supposed to represent them. I only showed one to be concise. I explicitly showed multiple members of `s`, in case anyone would suggest that alignment could be an issue. `x4` and `x5` are only used in a special corner case. Most of the C library is not concerned with them. – SU3 May 18 '18 at 05:10
  • @PasserBy: That (unfortunately) is a matter of opinion. If the C function is actually `init_1_3(s* x)` and writes to `x->x1` through `x->x3`, it's not UB on the C side, and for interoperability it has to be defined by C++ as well. Now there is indeed a common claim that you can't create C++ objects by writing to uninitialized memory, but there's also a claim that C and C++ are still interoperable. The two can't be true at the same time. And actual compilers choose the second interpretation. – MSalters May 18 '18 at 09:18
  • @MSalters There is a full discussion about that [here](https://stackoverflow.com/questions/46909105/existence-of-objects-created-in-c-functions). Regardless, since there isn't sufficient storage to even store all the `s`, there is no ambiguity in that they don't exist. If they do exist, what do you think `(*s)(mem + stuff_before)[0].x4` should mean? – Passer By May 18 '18 at 12:10
  • @MSalters: From what I can tell, compilers don't even support `structType1 *p1 = &unionArray[i]->member1; int temp1 = p1->x; structType2 *p2 = &unionArray[j]->member2; p2->x=4; structType1 *p3 = &unionArray[i]->member1; int temp2 = p3->x;`. Even though both `p2` and `p3` are visibly derived from the same root lvalue `unionArray`, and even though all operations on `p2` precede the derivation of `p3`, gcc and clang both regard the write to `p2->x` as unsequenced relative to the read of `p3->x`. – supercat May 18 '18 at 19:00

2 Answers2

1

Provided you avoid any alignment-related issues, your code would fall in the category of programs which the authors of the Standard might have expected quality implementations to process usefully, but which section N1570 6.5p7 of the Standard would allow implementations to process in arbitrary fashion. Note that the way the C Standard is written, even something like:

struct foo {int x;} s = {0};
s.x = 1;

would fall in that same category because the Standard does not describe any circumstances in which an lvalue of type int may be used to affect an object of type struct foo, nor does its definition of lvalue accommodate the idea that an lvalue may have a type of int but have an 6.5p7 association with some other type.

There is no reason why any quality compiler should have any difficulty recognizing that code which casts a T1* to a T2* and passes it to a function that acts upon a T2* might access an object of type T1*. In the absence of the -fno-strict-aliasing flag, gcc nor clang are willfully blind to such possibilities, but using that flag will make them behave like quality compilers.

supercat
  • 77,689
  • 9
  • 166
  • 211
0

As long as your function f is not dealing with x4 and x5 then you can safely copy structure into an unsigned char array with trimmed length and pass that to function.Sample program can be something like this :

#include <iostream>
#include <memory.h>
struct s {
  double x1;
  float x2;
  long long x3;
  int x4;
  int x5;
};
struct s_trimmed {
  double x1;
  float x2;
  long long x3;
};
void f(s* x)
{
  std::cout<<x->x1<<std::endl;
  std::cout<<x->x2<<std::endl;
  std::cout<<x->x3<<std::endl;
}
int main() {
  size_t nums = 2;
  unsigned char *mem = new unsigned char[nums*sizeof(s_trimmed)];
  unsigned int start = 0;
  for(int i=0;i<nums;i++)
  {
    s* s_obj = new s;
    s_obj->x1 = 10.5;
    s_obj->x2 = 89.98;
    s_obj->x3=28765;
    s_obj->x4=1;
    s_obj->x5=5;
    memcpy(mem+start,s_obj,sizeof(s_trimmed));
    start = start + sizeof(s_trimmed);
    delete s_obj;
    s_obj = nullptr;
  }
  start = 0;
  for(int i=0;i<nums;i++)
  {
    unsigned char *memf = new unsigned char[sizeof(s_trimmed)];
    memcpy(memf,mem+start,sizeof(s_trimmed));    
    f((s*)memf);    

  }
  delete[] mem;
  mem=nullptr;
  return 0;
}
PapaDiHatti
  • 1,841
  • 19
  • 26