Using joins in function parameters

I recently discovered that something is compiling (not sure if this is legal, though). My need for such things comes from this: My project outputs the machine code for the selected arch (which may or may not be the same arch as the one that runs the program). So, I would like to support up to 64-bit architectures right now (at the same time supporting existing 32 and 16-bit arcs.) My current solution for the new_state base is just to be uint64_t, and manually - up to 16 and 32 bits as needed Although, I found that you can compile joins in a function parameter. Thus, a function like this compiles:

int pcg_new_state(pcg_state *s,int arch,void *mem,int sz, union{ uint16_t b16; uint32_t b32; uint64_t b64; }base ,int self_running); 

Is such a thing generally "legal", though or supported by any other compilers? Also, I cannot figure out how to call this function without creating a union, and then passing this union to new_state.

+4
source share
5 answers

To summarize: Yes, this is valid in C, although it is illegal in C ++. The latter contains this note which explains the difference

Edit: In C ++, types cannot be defined in reverse order or in parameter types. In C, these type definitions are allowed.

Example:

 void f( struct S { int a; } arg ) {} // valid C, invalid C++ enum E { A, B, C } f() {} // valid C, invalid C++ 
  • Rationale: When comparing types in different compilation units, C ++ relies on name equivalence when C relies on structural equivalence. As for the types of parameters: since the type specified in the parameter list will be in the scope of the function, the only legal calls in C ++ will be performed inside the function itself.
  • Impact on the original function: Removing a semantically well-defined function.
  • The complexity of the transformation: Semantic transformation. Type definitions should be moved to the file area or to header files.
  • How widely used: Rarely. This type definition style is considered a bad coding style.

Structural equivalence in C is accomplished by the concept of type compatibility. This allows C to handle many types as if they were identical, although they are theoretically different - because they are declared in two different translation units. In C ++, this concept does not exist, because types have a binding and are mapped to the same object (for example, so that member functions can communicate with each other).

Note that the above explanation is based on C89, which does not take into account the name of the structure tag when determining type compatibility. In project C89, the corresponding text is read as follows:

In addition, two types of structure, association, or enumeration declared in separate translation units are compatible if they have the same number of members, the same member names, and compatible element types; for two structures, members must be in the same order;

In C99, type checking is more stringent: if one structure has a tag name, another structure declaration must have the same tag name. Therefore, in your case of type unnamed union, in order to declare a function in another TU that has a compatible type, you will need unnamed union again if you want to have a valid C99 code (without undefined behavior) - you cannot "cheat" and use the named union in one TU and an unnamed alliance in another TU. It seems to me that this "trick" is valid for C89. C99 TC3 6.2.7/1 :

In addition, two types of structure, union, or enumeration declared in separate translation units are compatible if their tags and members satisfy the following requirements: if declared by a tag, the other is declared with the same tag. If both are complete types, the following additional requirements apply: there must be a one-to-one correspondence between their members, so that each pair of the corresponding members is declared with compatible types and such that if one member of the corresponding pair is declared with a name, the other member is declared with the same by name. For two entities, the corresponding members must be declared in the same order.


The way you want to do this does not work. The function call converts the arguments to the parameter type, as if they were executed in the usual way.

So, for this you will have to have an argument compatible with the parameter type. For two unions declared in the same translation unit, this means that their type must be equal - this is the only way you can create a compatible type within the same translation unit. But this will not work, because the declaration of an unnamed union creates a unique new type - do not "refer" to it in any way using another declaration.

So, to summarize - you must specify the union name type. To avoid creating a separate variable to pass the necessary base argument, I would declare it outside the function and create functions that return a union that you can pass

 union base_type { uint16_t b16; uint32_t b32; uint64_t b64; }; int pcg_new_state(pcg_state *s,int arch,void *mem,int sz, union base_type base,int self_running); union base_type base_b16(uint16_t t) { union base_type b; b.b16 = t; return b; } union base_type base_b32(uint32_t t) { union base_type b; b.b32 = t; return b; } union base_type base_b64(uint64_t t) { union base_type b; b.b64 = t; return b; } 

Now it might look like this

 pcg_new_state(...., base_b32(4211), ....); 
+6
source

I quickly looked through the standard (Edit: C ++) and did not see anything forbidden in 8.3.5 - Functions [dcl.fct] or 9.5 - Unions [class.union]. As a semi-educated assumption, I would say that it is legal to transfer a union, but not to declare one such union. GCC gives:

error: types cannot be defined in parameter types.

So, you will need to determine the type ahead of time.

Even if it is legal, however, this does not mean that it is a good idea. Just by looking at it, I would suggest that overloading might provide a better solution. But, of course, you know that your code is best ... just thinking that you can look for a simpler and more idiomatic solution.

+1
source

How about this:

 typedef struct _base { union { uint16_t b16; uint32_t b32; uint64_t b64; }; }Base; int func(Base b) { return 0; } int _tmain(int argc, _TCHAR* argv[]) { Base b; b.b16 = 0xFFFF; func(b); return 0; } 
0
source

You can get this for compilation in GCC by providing a union with the name in the function, but you cannot use it because of the scope of the fix, as GCC warns:

 test_union.c:14: warning: 'union X' declared inside parameter list test_union.c:14: warning: its scope is only this definition or declaration, which is probably not what you want 

code:

 int pcg_new_state(pcg_state *s,int arch,void *mem,int sz, union X{ uint16_t b16; uint32_t b32; uint64_t b64; }base ,int self_running); 
0
source

The size of the union will be the size of the largest member, so you won’t get anything. You could just make the uint64_t parameter, as conversions from the larger and smaller unsigned types are well defined and usually cheap to implement. (For example, assigning uint64_t to uint16_t can only be done using the lower 16 bits of a wider type).

EDIT : e.g.

 int pcg_new_state(pcg_state *s,int arch,void *mem,int sz, uint64_t param_base ,int self_running) { /* Implementation for 16 bit arch */ uint16_t base = param_base; /* ... more code ... */ } 
0
source

All Articles