About Information Hiding Design Rules

M

matt

Hello group,

I'm trying to become familiar with the information hiding design rules, and I
have a lot (3) of questions for all you experts. AFAIK, a generic module has 2
files:

================
module.h
================
#ifndef __MODULE_HDR_INCLUDED__
#define __MODULE_HDR_INCLUDED__

typedef struct myadt_t {
void * pvt;
} myadt_t;

/* Define the public interface to myadt_t objects */

int myadt_create(myadt_t *);
int myadt_destroy(myadt_t *);
int myadt_get_status(myadt_t *, int *);

#endif

================
module.c
================
#include <stdlib.h>
#include <stdio.h>
#include "module.h"

typedef struct private_myadt_t {
int x;
int y;
} private_myadt_t;

int
myadt_create(myadt_t * ptr)
{
private_myadt_t * pvt;
ptr->pvt = malloc (sizeof(private_myadt_t));
if (ptr->pvt == NULL) {
printf("Memory couldn't be allocated\n");
return 0;
}
pvt = (private_myadt_t *) ptr->pvt;
pvt->x = 10;
pvt->y = 10;
return 1;
}

int
myadt_destroy(myadt_t * ptr)
{
free(ptr->pvt);
return 1;
}

int
myadt_get_status(myadt_t * ptr, int * status)
{
private_myadt_t * pvt;
if (!ptr->pvt) {
printf("Working with uninitialized data struct\n");
return 0;
}
pvt = (private_myadt_t *) ptr->pvt;
*status = pvt->x + pvt->y;
return 1;
}

and it can be used as follows in the main file:

==============
main.c
==============
#include <stdio.h>
#include "module.h"

int
main()
{
myadt_t ADT;
int status;
myadt_create(&ADT);
myadt_get_status(&ADT, &status);
myadt_destroy(&ADT);
printf("status: %d\n", status);
return 1;
}

My question are the following:

1) Is this a good way to proceed or is there way that is universally
recognized as *THE* way to use the information hiding technique in ANSI-C ?

2) With the code above, I can't prevent the use of uninitialized data
structures. That is, if I changed the main() to

#include <stdio.h>
#include "module.h"
int
main()
{
myadt_t ADT;
int status;
myadt_get_status(&ADT, &status); /* <-- PROBLEM HERE! */
printf("status: %d\n", status);
return 1;
}

I would expect that the message "Working with uninitialized data struct\n"
be displayed on the screen, but this does not occur because ptr->pvt is not
initialized to NULL. So, which test condition could I use in function
myadt_get_status() to prevent the use of uninitialized data struct?

3) In microcontroller programming, it often happens that the functions
malloc() and free() are not available. Is there a way (also a non-standard one)
to use information hiding design rules without using these functions?

Best Regards,

matt
 
M

matt

There is no way to examine an object and decide whether it
has or hasn't been initialized.

However there are programs like valgrind that can recognize whether you are
using uninitialised memory. Infact, if I run the example program with valgrind I
obtain

matt@isaac:~/work/c/information-hiding$ valgrind ./exe

[...]

==4581==
==4581== Use of uninitialised value of size 4
==4581== at 0x804849E: myadt_get_status (in
/home/matt/work/c/information-hiding/exe)
==4581== by 0x80484DE: main (in /home/matt/work/c/information-hiding/exe)
==4581==
==4581== Use of uninitialised value of size 4
==4581== at 0x80484A3: myadt_get_status (in
/home/matt/work/c/information-hiding/exe)
==4581== by 0x80484DE: main (in /home/matt/work/c/information-hiding/exe)
status: 953015467

[...]
However, this problem mostly goes away if you adopt the more
usual arrangement. Since myadt_create() is the source of all
myadt_t instances, making sure that myadt_create() initializes
the new instances properly solves the problem. Your client has
no way to create a myadt_t instance on its own, so your client
has no opportunity to forget to initialize one.

What do you mean with "properly"? Adopting this solution should suffice

myadt_t *
myadt_create(void)
{
myadt_t * ptr;
ptr = malloc (sizeof(myadt_t));
if (ptr == NULL) {
printf("Memory couldn't be allocated\n");
return 0;
}
ptr->x = 10;
ptr->y = 10;
return ptr;
}

.... isn't it?

Thanks for your time.

matt
 
T

Thad Smith

Eric said:
matt said:
Hello group,

I'm trying to become familiar with the information hiding design
rules, and I have a lot (3) of questions for all you experts. AFAIK, a
generic module has 2 files:

================
module.h
================
#ifndef __MODULE_HDR_INCLUDED__
#define __MODULE_HDR_INCLUDED__

Poor choice of macro name: Identifiers that start with two
underscores (or with an underscore and a capital letter) are
"reserved for any use," meaning that you shouldn't use them.
typedef struct myadt_t {
void * pvt;
} myadt_t;

/* Define the public interface to myadt_t objects */

int myadt_create(myadt_t *);
int myadt_destroy(myadt_t *);
int myadt_get_status(myadt_t *, int *);

#endif
[...]
My question are the following:

1) Is this a good way to proceed or is there way that is
universally recognized as *THE* way to use the information hiding
technique in ANSI-C ?
3) In microcontroller programming, it often happens that the
functions malloc() and free() are not available. Is there a way (also
a non-standard one) to use information hiding design rules without
using these functions?

One simple technique that has been around for a long time is to statically
allocate the maximum number of items within your module. When you call
your create function, it allocates the next available entry in the array
and returns the index.

The disadvantage of that technique is that you must determine and allocate
the maximum number of items.

Another way is to expose the structure and let the caller statically
allocate it, then pass a pointer to the routines.
 
R

rahul

3) In microcontroller programming, it often happens that the functions
malloc() and free() are not available. Is there a way (also a non-standard one)
to use information hiding design rules without using these functions?
You can have a linked list implemented on arrays. Something like:
#define MAX_SIZE ..
typedef struct _node {
Your_ADT data;
int next;
}node;
node store[MAX_SIZE];
static int free = 0; /* points to the current free node */

void initList(void) {
/* initialize the list */
for (i = 0; i < MAX_SIZE - 1; i++) {
store.next = i + 1;
}
store[MAX_SIZE - 1] = -1; /* stating that this is the end of the
list

Initially in your link list, every element is linked with the next
element. You will have to keep track of allocated and freed elements.
This is a crude approach but works. On the other hand, you can write
your own malloc() sort of thing. The implementation file will
internally maintain a statically allocated array of your ADT.
Individual elements of the array can be returned on successive calls.
On the other hand, if you wish to write generic malloc(), then
probably you can have a large static char array.
 
M

matt

3) In microcontroller programming, it often happens that the functions
malloc() and free() are not available. Is there a way (also a non-standard one)
to use information hiding design rules without using these functions?
You can have a linked list implemented on arrays. Something like:
#define MAX_SIZE ..
typedef struct _node {
Your_ADT data;
int next;
}node;
node store[MAX_SIZE];
static int free = 0; /* points to the current free node */

void initList(void) {
/* initialize the list */
for (i = 0; i < MAX_SIZE - 1; i++) {
store.next = i + 1;
}
store[MAX_SIZE - 1] = -1; /* stating that this is the end of the
list

Initially in your link list, every element is linked with the next
element. You will have to keep track of allocated and freed elements.
This is a crude approach but works. On the other hand, you can write
your own malloc() sort of thing. The implementation file will
internally maintain a statically allocated array of your ADT.
Individual elements of the array can be returned on successive calls.
On the other hand, if you wish to write generic malloc(), then
probably you can have a large static char array.


Yes, this could be a solution. However, I'm wondering if it could involve
performance issues in real-time applications. If this is the case, designing the
ADT as an opaque type by "asking" that users don't write code that depends on
how the ADT is implemented could still be considered a good solution?

In other words, how often are Information Hiding Design Rules used in
microcontrollers programming for real-time applications?
 
T

Thad Smith

matt said:
In other words, how often are Information Hiding Design Rules used in
microcontrollers programming for real-time applications?

Not often, in my experience. Part of the reason is that most applications
are written by fewer people, with a shared set of rules and priority of
goals that tend to preclude using extra code or execution time resources to
enforce abstract data typing or little benefit.

My typical practice is to define the full data structure in the module
header file, then document, with comments, that all operations with the foo
data should be done by calling the foo module functions. If I expect the
module to only need one data structure (i.e., THE serial output buffer), I
will typically code it as static memory in the foo module, instead of
allocating an instance.
 
N

Nathan Wagner

Eric Sosman wrote: said:
There is no way to examine an object and decide whether it
has or hasn't been initialized.

You could keep track of which pointers have been initialized. Then
if you get one you haven't seen yet, you can do the right thing.

I'm not suggesting that this is a *good* design, merely that it's
possible.
However, this problem mostly goes away if you adopt the more
usual arrangement. Since myadt_create() is the source of all
myadt_t instances, making sure that myadt_create() initializes
the new instances properly solves the problem.

Of course this pretty much requires malloc...

Which you address:
There are several things you could do. At one extreme, you
could write your own malloc() et al., perhaps backed by the memory
of one big statically-allocated array. At the other, you could
have a statically-allocated array of myadt_t instances, and could
keep track of which are currently in use and which are available.

This latter could be combined with just keeping track of initialized
pointers. Instead of statically allocating (say) 10 myadt_t objects,
you could statically allocate an array of 10 pointers to such
and keep your initialized pointer list in there.

Personally, I'm not a fan of information hiding. But if it really
wants to happen, some thought needs to be given to the expected
usage pattern before the exact interface can be decided on.
 
A

Antoninus Twink

Simply returning NULL discards information about the nature of the
failure.

Consider writing information messages to a log file instead of either
stdout or stderr.

Surely a simpler solution for a library function is to set errno?
 
R

rahul

Surely a simpler solution for a library function is to set errno?

Simpler and prevalent in Linux/Unix environment; it is more of a
personal preference but many a times I return defined constants.
Something like:
#define NODE_NOT_FOUND -10
#define EMPTY_LIST -11

int search(int i) {
/* if list empty */
return EMPTY_LIST;
/* if not found */
return NODE_NOT_FOUND;
}

But of course, errno is a better choice in certain scenarios, like
returning NULL and setting the errno on error; otherwise returning the
valid pointer.
 
M

matt

You are right. It's unlikely the client to forget to initialize an instance
of myadt_t...
Consider `ptr = malloc(sizeof *ptr);' as a safer alternative,
"safer" because there's less chance of an accidental mismatch.
Many's the time I've seen errors like

msghead_t *p = malloc(sizeof(msghead_t));
msgbody_t *q = malloc(sizeof(msghead_t));

... which the suggested idiom almost always avoids.


Consider writing error messages to stderr instead of to
stdout. Still better, consider writing no message at all, but
just returning the NULL and letting the client decide what to
do about the matter; the client has broader knowledge of the
context than you do.


Yes, this is what I meant: myadt_create() either returns
NULL or returns a pointer to a myadt_t instance that has been
initialized and is ready for use. There is no possibility that
the program can create a myadt_t instance in any other way,
hence no possibility of an uninitialized instance.

but, hey, errare humanum est! Please consider this main() program:

#include <stdio.h>
#include "module.h"
int
main()
{
myadt_t * ADT;
int status;

/*
* a lot of work here... (1)
*/

myadt_get_status(ADT, &status);
printf("status: %d\n", status);
return 1;
}

Here, being a very absent-minded man :), because of (1) I forgot to
initialize an instance of myadt_t. As result I obtain:

matt@isaac:~/work/c/information-hiding$ ./exe
status: 953015467

So, I wonder what do you think about the following solution:
============
module.c
============
....
struct myadt_t {
int x;
int y;
int uninitialised_data;
};
....

myadt_t *
myadt_create(void)
{
...
ptr->uninitialised_data = 0;
return ptr;
}
....

int
myadt_get_status(myadt_t * ptr, int * status)
{
if (ptr->uninitialised_data) {
printf("Working with uninitialized data struct\n");
return 0;
}
...
return 1;
}

In practice, IMHO, it's highly unlikely that the pointer ADT (in main())
contains the address of a memory location in which the integer corresponding to
"int uninitialized_data" has all its 32 bits set to zero. Therefore, the test

if (ptr->uninitialised_data) ...

always returns true, except after the function myadt_create() has been
called. Indeed, running the program I get:

matt@isaac:~/work/c/information-hiding$ ./exe
Working with uninitialized data struct

Would this solution be so awful?
 
F

fnegroni

To the benefit of the OP, I would recommend the book:

C Interfaces and Implementations: Techniques for Creating Reusable
Software
by David R. Hanson - Princeton University
Publisher: Addison Wesley Professional
Pub Date: August 20, 1996
Print ISBN-10: 0-201-49841-3
Print ISBN-13: 978-0-201-49841-7
eText ISBN-10: 0-321-56280-1
eText ISBN-13: 978-0-321-56280-7
Pages: 544

It has sections on well established idioms for ADT's: atoms, lists,
etc...

We have a printed and online version of the book at work and it has
proven itself worthy of recommendation.
 
C

Chad

matt said:
Hello group,
  I'm trying to become familiar with the information hiding design
rules, and I have a lot (3) of questions for all you experts. AFAIK, a
generic module has 2 files:
================
    module.h
================
#ifndef __MODULE_HDR_INCLUDED__
#define __MODULE_HDR_INCLUDED__

     Poor choice of macro name: Identifiers that start with two
underscores (or with an underscore and a capital letter) are
"reserved for any use," meaning that you shouldn't use them.




typedef struct myadt_t {
    void * pvt;
} myadt_t;
/* Define the public interface to myadt_t objects */
int myadt_create(myadt_t *);
int myadt_destroy(myadt_t *);
int myadt_get_status(myadt_t *, int *);
#endif
[...]

  My question are the following:
    1) Is this a good way to proceed or is there way that is universally
recognized as *THE* way to use the information hiding technique in ANSI-C ?

     "There are nine and sixty ways of constructing tribal lays,
      And every single one of them is right!"

     That said, the use of the "wrapper" struct is fairly unusual
and doesn't seem to add much.  An approach more commonly seen is

        /* myadt.h */
        typedef struct myadt_t /* no content */ myadt_t;
        myadt_t * myadt_create(void);
        int myadt_destroy(myadt_t *);
        int myadt_get_status(myadt_t *, int *);

     In this formulation, myadt_t is an "incomplete type:" the
compiler knows its name and knows how to manipulate pointers to
it, but doesn't know how big it is nor what it looks like on the
inside.  The details of what the struct actually contains remain
hidden in your implementation module:

        /* myadt.c */
        #include "myadt.h"
        struct myadt_t {
            int x;
            int y;
        };

... so your implementation can use them, but the clients can't.




    2) With the code above, I can't prevent the use of uninitialized
data structures. That is, if I changed the main() to
#include <stdio.h>
#include "module.h"
int
main()
{
    myadt_t ADT;
    int status;
    myadt_get_status(&ADT, &status); /* <-- PROBLEM HERE! */
    printf("status: %d\n", status);
    return 1;
}
    I would expect that the message "Working with uninitialized data
struct\n" be displayed on the screen, but this does not occur because
ptr->pvt is not initialized to NULL. So, which test condition could I
use in function myadt_get_status() to prevent the use of uninitialized
data struct?

     There is no way to examine an object and decide whether it
has or hasn't been initialized.  If it hasn't been given a value,
then the attempt to pluck a value from it produces undefined
behavior.  Even if you dodge the U.B. by examining the object's
individual bytes, the "garbage" in the object might resemble what
you'd see in a properly initialized object.

Can you or someone else please explain to me how examining an object
to see whether it has or hasn't been initialized produces undefined
behavior.


Chad
 
C

Chad

Chad said:
[...]
There is no way to examine an object and decide whether it
has or hasn't been initialized. If it hasn't been given a value,
then the attempt to pluck a value from it produces undefined
behavior. Even if you dodge the U.B. by examining the object's
individual bytes, the "garbage" in the object might resemble what
you'd see in a properly initialized object.
Can you or someone else please explain to me how examining an object
to see whether it has or hasn't been initialized produces undefined
behavior.

An uninitialized object with automatic storage duration
has an indeterminate initial value (6.2.4p5, 6.7.8p10, 6.8p3).
So, too, does any object in memory acquired from malloc() or
realloc() but not yet stored to (7.20.3.3p2, 7.20.3.4p2).

An indeterminate value can be a trap representation (3.17.2).
Taken along with the above, this means an uninitialized object
may hold a trap representation.

Reading a trap representation via an lvalue that is not
of character type -- hence the mention of accessing the bytes
individually -- produces undefined behavior (6.2.6.1p5).

--

I think I might be getting in over my head on the following question.
The OP had

int
myadt_get_status(myadt_t * ptr, int * status)
{
private_myadt_t * pvt;
if (!ptr->pvt) {
printf("Working with uninitialized data struct\n");
return 0;
}
pvt = (private_myadt_t *) ptr->pvt;
*status = pvt->x + pvt->y;
return 1;

}


I thought that !ptr->pvt was testing the the value associated with the
object and not the object itself.

Chad
 
N

Nick Keighley

matt wrote:

Not often, in my experience.

ERT covers a lot of ground these days... I've seen an ERT that made
some attempt
to info hide. The different parts communicated by message passing even
thoough they could have called functions directly. the different
layers
(think communication protocols) hid information from each other.
 Part of the reason is that most applications
are written by fewer people,

fewer than what? I think this system had about 10 at peak.
And they weren't the same 10. Joke for new starter "you were
still at school when this macro was defined"

with a shared set of rules and priority of
goals that tend to preclude using extra code or execution time resources to
enforce abstract data typing or little benefit.

its been ported to different architectures several times.

My typical practice is to define the full data structure in the module
header file, then document, with comments, that all operations with the foo
data should be done by calling the foo module functions.  If I expect the
module to only need one data structure (i.e., THE serial output buffer), I
will typically code it as static memory in the foo module, instead of
allocating an instance.

as I said ERT covers a lot of ground. How many lines of code are there
in your phone? Your car? An A380.

I know, none of these are Embedded Systems because they all have a
GUI!
 
A

Antoninus Twink

Ah, but what value do you store in errno? The values that are
guaranteed to be meaningful are EDOM, EILSEQ, and ERANGE, and that's
all. You could choose other values, positive or negative, but then
the use of perror() and strerror() becomes problematic.

This is just paranoid nonsense. Sensible platforms will provide many,
many useful error numbers, and perror() and strerror() will work just
fine with them.

For example, Linux provides 120 errors for you to choose from, many of
which are inherited from POSIX:

E2BIG Argument list too long
EACCES Permission denied
EADDRINUSE Address already in use
EADDRNOTAVAIL Address not available
EAFNOSUPPORT Address family not supported
EAGAIN Resource temporarily unavailable
EALREADY Connection already in progress
EBADE Invalid exchange
EBADF Bad file descriptor
EBADFD File descriptor in bad state
EBADMSG Bad message
EBADR Invalid request descriptor
EBADRQC Invalid request code
EBADSLT Invalid slot
EBUSY Device or resource busy
ECANCELED Operation canceled
ECHILD No child processes
ECHRNG Channel number out of range
ECOMM Communication error on send
ECONNABORTED Connection aborted
ECONNREFUSED Connection refused
ECONNRESET Connection reset
EDEADLK Resource deadlock avoided
EDEADLOCK Synonym for EDEADLK
EDESTADDRREQ Destination address required
EDOM Mathematics argument out of domain of function C99)
EDQUOT Disk quota exceeded
EEXIST File exists
EFAULT Bad address
EFBIG File too large
EHOSTDOWN Host is down
EHOSTUNREACH Host is unreachable
EIDRM Identifier removed
EILSEQ Illegal byte sequence C99)
EINPROGRESS Operation in progress
EINTR Interrupted function call
EINVAL Invalid argument
EIO Input/output error
EISCONN Socket is connected
EISDIR Is a directory
EISNAM Is a named type file
EKEYEXPIRED Key has expired
EKEYREJECTED Key was rejected by service
EKEYREVOKED Key has been revoked
EL2HLT Level 2 halted
EL2NSYNC Level 2 not synchronized
EL3HLT Level 3 halted
EL3RST Level 3 halted
ELIBACC Cannot access a needed shared library
ELIBBAD Accessing a corrupted shared library
ELIBMAX Attempting to link in too many shared libraries
ELIBSCN lib section in a.out corrupted
ELIBEXEC Cannot exec a shared library directly
ELOOP Too many levels of symbolic links
EMEDIUMTYPE Wrong medium type
EMFILE Too many open files
EMLINK Too many links
EMSGSIZE Message too long
EMULTIHOP Multihop attempted
ENAMETOOLONG Filename too long
ENETDOWN Network is down
ENETRESET Connection aborted by network
ENETUNREACH Network unreachable
ENFILE Too many open files in system
ENOBUFS No buffer space available(XSI STREAMS option))
ENODATA No message is available on the STREAM head read queue
ENODEV No such device
ENOENT No such file or directory
ENOEXEC Exec format error
ENOKEY Required key not available
ENOLCK No locks available
ENOLINK Link has been severed
ENOMEDIUM No medium found
ENOMEM Not enough space
ENOMSG No message of the desired type
ENONET Machine is not on the network
ENOPKG Package not installed
ENOPROTOOPT Protocol not available
ENOSPC No space left on device
ENOSR No STREAM resources(XSI STREAMS option))
ENOSTR Not a STREAM(XSI STREAMS option))
ENOSYS Function not implemented
ENOTBLK Block device required
ENOTCONN The socket is not connected
ENOTDIR Not a directory
ENOTEMPTY Directory not empty
ENOTSOCK Not a socket
ENOTSUP Operation not supported
ENOTTY Inappropriate I/O control operation
ENOTUNIQ Name not unique on network
ENXIO No such device or address
EOPNOTSUPP Operation not supported on socket
EOVERFLOW Value too large to be stored in data type
EPERM Operation not permitted
EPFNOSUPPORT Protocol family not supported
EPIPE Broken pipe
EPROTO Protocol error
EPROTONOSUPPORT Protocol not supported
EPROTOTYPE Protocol wrong type for socket
ERANGE Result too large C99)
EREMCHG Remote address changed
EREMOTE Object is remote
EREMOTEIO Remote I/O error
ERESTART Interrupted system call should be restarted
EROFS Read-only file system
ESHUTDOWN Cannot send after transport endpoint shutdown
ESPIPE Invalid seek
ESOCKTNOSUPPORT Socket type not supported
ESRCH No such process
ESTALE Stale file handle
ESTRPIPE Streams pipe error
ETIME Timer expired
ETIMEDOUT Connection timed out
ETXTBSY Text file busy
EUCLEAN Structure needs cleaning
EUNATCH Protocol driver not attached
EUSERS Too many users
EWOULDBLOCK Operation would block
EXDEV Improper link
EXFULL Exchange full
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top