Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.c > #67649 > unrolled thread

type safety on simple data types - without structs?

Started bymathog <dmathog@gmail.com>
First post2015-08-17 15:52 -0700
Last post2015-08-21 13:47 -0700
Articles 5 — 4 participants

Back to article view | Back to comp.lang.c


Contents

  type safety on simple data types - without structs? mathog <dmathog@gmail.com> - 2015-08-17 15:52 -0700
    Re: type safety on simple data types - without structs? Ian Collins <ian-news@hotmail.com> - 2015-08-18 11:11 +1200
      Re: type safety on simple data types - without structs? Jorgen Grahn <grahn+nntp@snipabacken.se> - 2015-08-18 18:16 +0000
    Re: type safety on simple data types - without structs? Malcolm McLean <malcolm.mclean5@btinternet.com> - 2015-08-18 04:14 -0700
      Re: type safety on simple data types - without structs? mathog <dmathog@gmail.com> - 2015-08-21 13:47 -0700

#67649 — type safety on simple data types - without structs?

Frommathog <dmathog@gmail.com>
Date2015-08-17 15:52 -0700
Subjecttype safety on simple data types - without structs?
Message-ID<mqtoj6$8co$1@dont-email.me>
I'm working on a bit of code which has some packed data types.  These 
have records that are funny lengths, like 11 bytes, so the data is 
accessed by a bunch of get()/set() routines which either take a char *
pointer to the start of one of these records, or an integer index for 
the record number.  For the sake of argument, let all of those indices 
be uin64_t.

The question is, is there some magic way to make a type safe version of 
these simple types in C, perhaps something added in c99 or later?

I mean:

magic_here char * type_safe_char_ptr_A;
magic_here char * type_safe_char_ptr_B;

where "magic_here" has the same syntax as typedef, such that

int get_A(type_safe_char_ptr_A ptr){
   // do something
}

will throw an error at compilation time if it is called like:

type_safe_char_ptr_B bptr;
type_safe_char_ptr_A aptr;
// bptr assigned
int result = get_A(bptr); // wrong type in call
aptr=bptr; // wront type in assignment

Ditto for the uint64_t values.


typedef definitely does not fit the bill.  For instance, this compiles 
with no warnings;

typedef unsigned long long BUNSLLI;
typedef unsigned long long UNSLLI;

int wacko(UNSLLI var){
   if(var)return(1);
   return(0);
}

// later, in another routine

   BUNSLLI foo=1;
   UNSLLI  bar=0;
   bar=foo;
fprintf(stderr,"foo %llu bar %llu\n",foo,bar);
fprintf(stderr,"wacko %d\n",wacko(foo));


Type safety can be obtained by declaring a bunch of structs that contain 
only the one variable, like

typedef struct is_safe_char_ptr_A    SAFE_PTR_A;
typedef struct is_safe_char_ptr_B    SAFE_PTR_B;
typedef struct is_safe_uint64_A      SAFE_UINT64_A;
typedef struct is_safe_uint64_B      SAFE_UINT64_B;
struct is_safe_char_ptr_A { char *ptr;    };
struct is_safe_uint64_A {   uint64_t val; };
struct is_safe_char_ptr_B { char *ptr;    };
struct is_safe_uint64_B {   uint64_t val; };

then

int get_A(SAFE_PTR_A first, SAFE_UINT64_A index){
}

will not accept SAFE_PTR_B for the first, and SAFE_UINT64_B for the 
second.  The down side is that with this approach one must use

   first.ptr

and

   index.val

to get to the desired values.  In this example there is no offset
to either of the desired values, and there are no other values in
the structs. So the struct method seems a bit clunky, in that one must 
use an "access member" syntax when it seems like it shouldn't be needed.

I can see why this might not exist though.  For instance what is the 
compiler supposed to do with:

magic_here UINT64_t type_safe_UINT64_B;
magic_here UINT64_t type_safe_UINT64_A;
type_safe_UINT64_B bvar=100;

if it is supposed to throw an error on assignment of
type_safe_UINT64_A to an type_safe_UINT64_B, it would probably have to
do so for an integer assignment as well, unless there is an explicit cast.

Thanks,

David Mathog

[toc] | [next] | [standalone]


#67653

FromIan Collins <ian-news@hotmail.com>
Date2015-08-18 11:11 +1200
Message-ID<d3f80eF2rqoU1@mid.individual.net>
In reply to#67649
mathog wrote:
> I'm working on a bit of code which has some packed data types.  These
> have records that are funny lengths, like 11 bytes, so the data is
> accessed by a bunch of get()/set() routines which either take a char *
> pointer to the start of one of these records, or an integer index for
> the record number.  For the sake of argument, let all of those indices
> be uin64_t.
>
> The question is, is there some magic way to make a type safe version of
> these simple types in C, perhaps something added in c99 or later?

I don't think there is in C99, but if you consider C++ to be something 
added later then the answer is yes :)

-- 
Ian Collins

[toc] | [prev] | [next] | [standalone]


#67747

FromJorgen Grahn <grahn+nntp@snipabacken.se>
Date2015-08-18 18:16 +0000
Message-ID<slrnmt6tkb.eij.grahn+nntp@frailea.sa.invalid>
In reply to#67653
On Mon, 2015-08-17, Ian Collins wrote:
> mathog wrote:
>> I'm working on a bit of code which has some packed data types.  These
>> have records that are funny lengths, like 11 bytes, so the data is
>> accessed by a bunch of get()/set() routines which either take a char *
>> pointer to the start of one of these records, or an integer index for
>> the record number.  For the sake of argument, let all of those indices
>> be uin64_t.
>>
>> The question is, is there some magic way to make a type safe version of
>> these simple types in C, perhaps something added in c99 or later?
>
> I don't think there is in C99, but if you consider C++ to be something 
> added later then the answer is yes :)

And: to some of us it's one of the big features of C++: it lets you
arrange your code around a lot of small and rather trivial types.

The way to do it in C is indeed using structs.  It works, but it's
a lot more clunky and not always worth it.

/Jorgen

-- 
  // Jorgen Grahn <grahn@  Oo  o.   .     .
\X/     snipabacken.se>   O  o   .

[toc] | [prev] | [next] | [standalone]


#67701

FromMalcolm McLean <malcolm.mclean5@btinternet.com>
Date2015-08-18 04:14 -0700
Message-ID<f1de5bec-5e5f-4848-b751-b389ddc69ee9@googlegroups.com>
In reply to#67649
On Monday, August 17, 2015 at 11:54:11 PM UTC+1, mathog wrote:
>
> magic_here char * type_safe_char_ptr_A;
> magic_here char * type_safe_char_ptr_B;
> 
> where "magic_here" has the same syntax as typedef, such that
> 
> int get_A(type_safe_char_ptr_A ptr){
>    // do something
> }
> 
> will throw an error at compilation time if it is called like:
> 
>  In this example there is no offset
> to either of the desired values, and there are no other values in
> the structs. So the struct method seems a bit clunky, in that one must 
> use an "access member" syntax when it seems like it shouldn't be needed.
> 
I'd say that the struct method isn't clunky, unless you need to
micro-optimise.

We've got a buffer of packed bytes somewhere. As it happens, it must
be either all A or all B, and A and B are always a whole number of bytes.
So we can use an unsigned char *. But should any of that change, 
you're going to need extra bits in what is effectively a smart pointer.
So the struct method is robust, it scales to changes in requirements.

In fact what I'd do is just have one struct, buffer_ptr. Then have a
field that says whether it is A or B, and assert fail if you try to read
a A from a B pointer, or vice versa. That's essentially as good as type
safety, and it allows you to unify a lot of higher-level code
because you can switch on the type field in a low-level subroutine.
(That might not make sense, you have't given any details about the real
application, but there's a good chance it does make sense).


[toc] | [prev] | [next] | [standalone]


#67964

Frommathog <dmathog@gmail.com>
Date2015-08-21 13:47 -0700
Message-ID<mr82ms$u7e$1@dont-email.me>
In reply to#67701
Malcolm McLean wrote:
> On Monday, August 17, 2015 at 11:54:11 PM UTC+1, mathog wrote:
>>
>> magic_here char * type_safe_char_ptr_A;
>> magic_here char * type_safe_char_ptr_B;
>>
>> where "magic_here" has the same syntax as typedef, such that
>>
>> int get_A(type_safe_char_ptr_A ptr){
>>     // do something
>> }
>>
>> will throw an error at compilation time if it is called like:
>>
>>   In this example there is no offset
>> to either of the desired values, and there are no other values in
>> the structs. So the struct method seems a bit clunky, in that one must
>> use an "access member" syntax when it seems like it shouldn't be needed.
>>
> I'd say that the struct method isn't clunky, unless you need to
> micro-optimise.

I tried it and it wasn't all that terrible looking when it was finished. 
  It wasn't perfect though.  One of the types was
a uint32_t record number which was stored in an array.
A struct with a "payload" like this:

    uint32_t val;

has a sizeof() on the 64bit system of 8 and not 4, so it wasn't possible 
to cast the array and pull out the struct based types directly - even 
though all the uint32_t's were aligned properly for the data type, they 
were not aligned for the struct.

All in all though, it was still better with with the extra level of type 
safety, since there were locations with 2 or 3 different types of 
integer indices and their associated char * pointers in play, and this 
keeps them from being used accidentally in the wrong places.

I suppose some compilers may have a switch that would force the size of 
the struct down to something smaller than the native machine's word 
size, but that isn't portable as far as the language is concerned.

Of course, the problem mentioned above for the 32 bit type would also
occur for all 64bit ints on a 128 bit machine, should this code ever 
encounter such a beast.

Regards,

David Mathog

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.c


csiph-web