General rules on interface (function) design

L

lovecreatesbeauty

Could you talk something about the general rules on the interface
(function) design in C program that recognized publically? Or introduce
some good advice of yourself.

How do you think about some of those advices like following?

a. keep the interface clean and clear (What does clean or clear mean
and how to achieve that?).
b. avoid using static variables in local function if possible.
c. avoid using global variables for local function if possible.
d. avoid allocating dynamic memory in local function if possible.
....

And I write following the function to convert an integer to a string.
Your advices are welcome and appreciated.

Requirement: convert an integer to character string, put one blank char
among the string, for example: "1 2 3 4 0 9" for 123409.

/* s : hold the string presentation of an integer.
* num : an integer is to be gotten its string presentation.
*/
void itoa2(char *s, int num)
{
int len = 100;
char s1[len]; /* C99 features involved */
sprintf(s1, "%d", num);
int i = 0, j = 0;
for (i = 0; s1; ++i, ++j)
{
s[j] = s1;
s[++j] = ' ';
}
s[--j] = '\0';
}

How do you think about following choices of different interface design:

/* #2 self-contained or self-sufficient */
void itoa2(char *s, int num);

/* #3 global or static variables or dynamic memory may be required */
void itoa3(int num);
 
R

Robert Latest

On 23 May 2006 07:19:18 -0700,
in Msg. said:
How do you think about some of those advices like following?

a. keep the interface clean and clear (What does clean or clear mean
and how to achieve that?).
b. avoid using static variables in local function if possible.
c. avoid using global variables for local function if possible.
d. avoid allocating dynamic memory in local function if possible.

1. What are "local functions"?

2. If I avoid using global variables "for" local functions, what's
the point of having global variables in the first place?
How do you think about following choices of different interface design:

/* #2 self-contained or self-sufficient */
void itoa2(char *s, int num);

/* #3 global or static variables or dynamic memory may be required */
void itoa3(int num);

Well, "how" do YOU think about them?

#3 certainly has the least cluttered interface; you could unclutter it
even more without hurting functionality or breaking additional coding
standards rules:

void itoa4(void);

robert

PS: Think before you post, and let the success of the thinking reflect
in your posting. But I guess this advice is lost on "Google Groups"
posters.
 
R

Richard Heathfield

lovecreatesbeauty said:
Could you talk something about the general rules on the interface
(function) design in C program that recognized publically? Or introduce
some good advice of yourself.

How do you think about some of those advices like following?

a. keep the interface clean and clear (What does clean or clear mean
and how to achieve that?).

Like many short questions, this one doesn't have a useful short answer. One
or two hints for clean, clear interfaces:

(1) avoid hiding *s in typedefs (some people would say to avoid typedefs
too, but I'm not one of them);
(2) minimise coupling - the dependence of a function on other functions, on
file scope objects, and on "state" generally;
(3) aim for consistency (but not to the point of foolishness) in your
interfaces;
(4) don't publish unnecessary information (for example, maybe your module
uses a binary search tree internally, but probably your users don't need to
know this - telling them is likely to have the effect of making them rely
on the fact, thus perhaps breaking their code when you switch to a hash
table later on).

That list is nothing like exhaustive. I'm afraid that it's far harder to
define the characteristics of a clean, clear interface than it is to
recognise them when you see them!
b. avoid using static variables in local function if possible.

Absolutely. For one thing, a stateless function is easier to grok than a
function containing statics. For another, although C doesn't support
multithreading, many C implementations do - and statics are not
thread-safe.
c. avoid using global variables for local function if possible.

The problems with file scope objects are:

(1) they introduce strong coupling between functions, making it harder to
lift a function out of a program and stick it in a library;
(2) because any function that can see the object can change its value,
debugging is made unnecessarily hard;
(3) shadowing can become a serious problem.

d. avoid allocating dynamic memory in local function if possible.
Why?

And I write following the function to convert an integer to a string.
Your advices are welcome and appreciated.

Requirement: convert an integer to character string, put one blank char
among the string, for example: "1 2 3 4 0 9" for 123409.

/* s : hold the string presentation of an integer.
* num : an integer is to be gotten its string presentation.
*/
void itoa2(char *s, int num)
{
int len = 100;
char s1[len]; /* C99 features involved */
sprintf(s1, "%d", num);
int i = 0, j = 0;
for (i = 0; s1; ++i, ++j)
{
s[j] = s1;
s[++j] = ' ';
}
s[--j] = '\0';
}


You can remove the dependence on C99 as follows:

void itoa2(char *s, int num)
{
char s1[(sizeof(int) * CHAR_BIT + 8) / 3] = {0};
char *t = s1;
sprintf(s1, "%d", num);
while(*t != '\0')
{
*s++ = *t++;
*s++ = ' ';
}
*--s = '\0';
}
How do you think about following choices of different interface design:

/* #2 self-contained or self-sufficient */
void itoa2(char *s, int num);

It may be convenient to the user if you were to return a pointer to the null
terminator.
/* #3 global or static variables or dynamic memory may be required */
void itoa3(int num);

No point. It introduces unnecessary complexity.
 
L

lovecreatesbeauty

Richard Heathfield wrote:
You can remove the dependence on C99 as follows:

void itoa2(char *s, int num)
{
char s1[(sizeof(int) * CHAR_BIT + 8) / 3] = {0};
char *t = s1;
sprintf(s1, "%d", num);
while(*t != '\0')
{
*s++ = *t++;
*s++ = ' ';
}
*--s = '\0';
}

Thank Richard for the reply.

One more thing is how to do the exception/error check. For example the
availability of the parameter "s" of the function itoa2 doesn't be
validated. How to guarantee the pointer parameters in those aspects (I
also didn't check the pointer parameter in my code snippet):

1. does it refer to a valid memory?
2. is the size of the memory enough for holding the data?

How about the exception handling strategies on other program problems
that much more attention should be paid to?
 
R

Richard Heathfield

lovecreatesbeauty said:

One more thing is how to do the exception/error check. For example the
availability of the parameter "s" of the function itoa2 doesn't be
validated.

s is certainly available. Whether it is a valid pointer to sufficient memory
is something which itoa2 cannot ascertain. It must trust the caller.
Therefore, the function's documentation should make it clear that at least
2 * (sizeof(int) * CHAR_BIT + 5) / 3 + 1 bytes should be available via s.

And then it is the caller's responsibility to ensure that this is in fact
the case.
 
E

Eric Sosman

lovecreatesbeauty wrote On 05/23/06 10:19,:
Could you talk something about the general rules on the interface
(function) design in C program that recognized publically? Or introduce
some good advice of yourself.

How do you think about some of those advices like following?

a. keep the interface clean and clear (What does clean or clear mean
and how to achieve that?).

"For motherhood and against sin." (A parodist's summary
of politicians' campaign posturings.)
b. avoid using static variables in local function if possible.

See Richard Heathfield's response. Also, what do you mean
by a "local" function, and how does it differ from "national"
functions or "remote" functions or "express" functions?
c. avoid using global variables for local function if possible.

In addition to the points raised by R.H., note that a file-
scope variable has static storage duration and thus shares the
drawbacks of (b).
d. avoid allocating dynamic memory in local function if possible.

Hogwash. Allocate memory when you need it and as it's
needed. The important thing isn't where it's allocated, but
that it's properly kept track of.
And I write following the function to convert an integer to a string.
Your advices are welcome and appreciated.

Requirement: convert an integer to character string, put one blank char
among the string, for example: "1 2 3 4 0 9" for 123409.

/* s : hold the string presentation of an integer.
* num : an integer is to be gotten its string presentation.
*/
void itoa2(char *s, int num)
{
int len = 100;
char s1[len]; /* C99 features involved */

No need, as shown in R.H.'s post. (However, I see no
reason to initialize the array to all zeroes as he does.)
sprintf(s1, "%d", num);
int i = 0, j = 0;
for (i = 0; s1; ++i, ++j)
{
s[j] = s1;
s[++j] = ' ';
}
s[--j] = '\0';
}

How do you think about following choices of different interface design:

/* #2 self-contained or self-sufficient */
void itoa2(char *s, int num);


Unattractive, because it ignores the lesson of gets().
Two alternatives seem plausible:

- Let the caller provide both a buffer pointer and a
buffer size, and the function promises to honor the
size. If the buffer is too small for the number,
the function should be able to return a value that
describes the difficulty. See snprintf().

- In the header that declares itoa2(), #define a macro
named ITOA2BUFSIZE or some such. Require (in the
documentation) that the caller provide at buffer of
at least ITOA2BUFSIZE characters.
/* #3 global or static variables or dynamic memory may be required */
void itoa3(int num);

This signature appears to require a global variable to
communicate the result; I don't like it. The variable might
be the result buffer or a pointer to a dynamically-allocated
buffer or a FILE* stream that the caller can rewind and read
back again ... No matter; I don't like it.
 
J

jacob navia

lovecreatesbeauty a écrit :
Richard Heathfield wrote:
You can remove the dependence on C99 as follows:

void itoa2(char *s, int num)
{
char s1[(sizeof(int) * CHAR_BIT + 8) / 3] = {0};
char *t = s1;
sprintf(s1, "%d", num);
while(*t != '\0')
{
*s++ = *t++;
*s++ = ' ';
}
*--s = '\0';
}


Thank Richard for the reply.

One more thing is how to do the exception/error check. For example the
availability of the parameter "s" of the function itoa2 doesn't be
validated. How to guarantee the pointer parameters in those aspects (I
also didn't check the pointer parameter in my code snippet):

1. does it refer to a valid memory?
2. is the size of the memory enough for holding the data?

How about the exception handling strategies on other program problems
that much more attention should be paid to?

That interface is completely screwed up.
You need to pass the length of the buffer s.
You should return a result: true if you converted anything,
false if you did not, at the very least!

Better would be to return the number of characters processed
to the buffer.
 
R

Richard Heathfield

Eric Sosman said:
lovecreatesbeauty wrote On 05/23/06 10:19,:
int len = 100;
char s1[len]; /* C99 features involved */

No need, as shown in R.H.'s post. (However, I see no
reason to initialize the array to all zeroes as he does.)

Having been bitten twice by uninitialised objects (in production code), I am
perhaps a little paranoid about this. But I'd rather be paranoid than dead.

<snip>
 
A

August Karlstrom

Eric said:
- Let the caller provide both a buffer pointer and a
buffer size, and the function promises to honor the
size. If the buffer is too small for the number,
the function should be able to return a value that
describes the difficulty. See snprintf().

Why not simply define the procedure as

void GetIntAsString(char s[], int i);

and let the caller guarantee that s is long enough?


August
 
R

Richard Heathfield

August Karlstrom said:
Eric said:
- Let the caller provide both a buffer pointer and a
buffer size, and the function promises to honor the
size. If the buffer is too small for the number,
the function should be able to return a value that
describes the difficulty. See snprintf().

Why not simply define the procedure as

void GetIntAsString(char s[], int i);

and let the caller guarantee that s is long enough?

Personally, I agree, since the maximum length s needs to be is well-defined.
This is not a gets()-style problem; the function never writes to more than
a trivially calculable number of chars.
 
E

Eric Sosman

August Karlstrom wrote On 05/23/06 13:07,:
Eric said:
- Let the caller provide both a buffer pointer and a
buffer size, and the function promises to honor the
size. If the buffer is too small for the number,
the function should be able to return a value that
describes the difficulty. See snprintf().


Why not simply define the procedure as

void GetIntAsString(char s[], int i);

and let the caller guarantee that s is long enough?

That's pretty close to my second suggestion, which was
to "publish" the maximum number of characters the function
might use. That number can be derived from the values in
<limits.h>, so the caller could perfectly well calculate it
for himself, but I think it would be "friendlier" to provide
the value in an easy-to-use form.
 
K

Keith Thompson

Richard Heathfield said:
August Karlstrom said:
Eric said:
- Let the caller provide both a buffer pointer and a
buffer size, and the function promises to honor the
size. If the buffer is too small for the number,
the function should be able to return a value that
describes the difficulty. See snprintf().

Why not simply define the procedure as

void GetIntAsString(char s[], int i);

and let the caller guarantee that s is long enough?

Personally, I agree, since the maximum length s needs to be is well-defined.
This is not a gets()-style problem; the function never writes to more than
a trivially calculable number of chars.

It's trivally calculable, but it's also trivially miscalculable.

For example, someone using this function might assume that an int
won't be bigger than 32 bits, so the program will fail when ported to
a system with 64-bit ints -- or the caller might forget to leave room
for the sign.

One approach is to require the user to specify, in another argument,
the actual size of the string; this would at least allow the function
to detect a user error.

The maximum length needed for an int is fairly easy to calculate, and
a case can be made that getting it wrong is the caller's fault. But
if this is one of a set of similar functions for various types, you'll
probably want something like this anyway (how many characters to you
need to provide for a long double, or a pointer?).

On the other hand, that does make the interface more complex.
 
M

Malcolm

Richard Heathfield said:
One problem is that the allocation can always fail.
maybe you are allocating a trivial number of bytes to hold a filename, on a
system which needs a gigabyte of memory to runyour program. The chance of
the computer running out of memory may be less than the chance of it
breaking, but you still have to pass up an error return to be formally
correct. then the caller has to handle it somehow.

Another problem is that you might want to port to a system that doesn't have
dynamic memory.

These considerations were why strdup() wasn't included in the standard
library.
 
E

Eric Sosman

Malcolm wrote On 05/23/06 15:22,:
One problem is that the allocation can always fail.
maybe you are allocating a trivial number of bytes to hold a filename, on a
system which needs a gigabyte of memory to runyour program. The chance of
the computer running out of memory may be less than the chance of it
breaking, but you still have to pass up an error return to be formally
correct. then the caller has to handle it somehow.

This is an argument for avoiding *any* operation that
might fail: No fopen(), no time(), no getenv(), no ...
One wonders what would remain.
Another problem is that you might want to port to a system that doesn't have
dynamic memory.

The requirements of free-standing systems are special.
Again, though, this argues against calling printf(), sqrt(),
longjmp(), strlen(), exit(), ...
These considerations were why strdup() wasn't included in the standard
library.

Are you sure this was the reason? I can find nothing
about the matter in the Rationale; what did I overlook?
 
S

santosh

Malcolm said:
One problem is that the allocation can always fail.
maybe you are allocating a trivial number of bytes to hold a filename, on a
system which needs a gigabyte of memory to runyour program. The chance of
the computer running out of memory may be less than the chance of it
breaking, but you still have to pass up an error return to be formally
correct. then the caller has to handle it somehow.

I suppose, the so called "rules" for function interfaces are not cast
in stone and will vary according to the target environment and the
programmer's/client's aims and requirements.

Maybe dynamic memory allocation is rather more costly/risky in
low-resource enviroments like the embedded work, but if it is advised
against under general PC/Mainframe area, then not much can be
accomplished in C.

Further static memory allocation has it's own problems like
inflexibility, and propensity for buffer overflows.
Another problem is that you might want to port to a system that doesn't have
dynamic memory.

Yes, but if general "rules" like these should be provided at all, I
would think that they would tend to target the "common" case. I may be
wrong here, but it seems to me that systems incapable of dynamic memory
allocations would not be the "common" case.
 
M

Malcolm

santosh said:
Yes, but if general "rules" like these should be provided at all, I
would think that they would tend to target the "common" case. I may be
wrong here, but it seems to me that systems incapable of dynamic memory
allocations would not be the "common" case.
It's more common than you might imagine not to have dynamic memory.
If you write only for PCs and UNIX boxes then of course you don't need to
worry, but a lot of C doesn't run under those operating systems.
 
R

Richard Heathfield

Malcolm said:
One problem is that the allocation can always fail.

Is this the same Malcolm we know and love? :)

It's certainly true that the allocation can fail, yes. The possibility of
failure should not stop us from reaping the rewards of success. It does
mean, however, that we should anticipate the failure and handle it as
gracefully as possible.

Another problem is that you might want to port to a system that doesn't
have dynamic memory.

These considerations were why strdup() wasn't included in the standard
library.

ISTR that the actual reason was that they couldn't make up their minds
whether the prototype belonged in stdlib.h or string.h :)
 
L

lovecreatesbeauty

Robert said:
On 23 May 2006 07:19:18 -0700,
1. What are "local functions"?

I am sorry to use a wrong word, please ignore it. English is not my
mother language, but Chinese is. I am not a excellent C programmer, so
come to learn from you all. Thank you.
PS: Think before you post, and let the success of the thinking reflect
in your posting.

I agree with you. One problem to me is that I can not use a good
English, so sorry. I'm improving the expressing skills in language.
But I guess this advice is lost on "Google Groups"
posters.

I think it is not important whether one use Google groups or some news
reader.

Thanks for so much good advice and great help from you all.
 
L

lovecreatesbeauty

Richard said:
s is certainly available. Whether it is a valid pointer to sufficient memory
is something which itoa2 cannot ascertain. It must trust the caller.
Therefore, the function's documentation should make it clear that at least
2 * (sizeof(int) * CHAR_BIT + 5) / 3 + 1 bytes should be available via s.

And then it is the caller's responsibility to ensure that this is in fact
the case.

But I saw some programmers (in some programs or even in some C books)
checked if the parameter s is NULL, though the memory size pointed by
the parameter s can not be detected. Or even assert() was used to
check/guarantee the pointer parameter availability at these occassion.

void itoa2(char *s, int num)
{

/* ... */

/* assert(s); */

if (s == NULL)
{
return; /* invalid parameters */
}

/* ... */
}
 
R

Richard Heathfield

lovecreatesbeauty said:
But I saw some programmers (in some programs or even in some C books)
checked if the parameter s is NULL,

Sorry, I should have mentioned NULL. Yes, if s is NULL then it's definitely
invalid. But if s is /not/ NULL, then it might be valid and it might not,
and you can't tell just by looking at it.

though the memory size pointed by
the parameter s can not be detected.
Right.

Or even assert() was used to
check/guarantee the pointer parameter availability at these occassion.

void itoa2(char *s, int num)
{

/* ... */

/* assert(s); */

If you're going to use assert to check a pointer against NULL (which is
sometimes a good idea and sometimes not), check it against NULL!

assert(s != NULL);

In C99, assert(s) is fine, because they changed the rules, but almost nobody
has a conforming C99 compiler, so you're better off with an integer
expression than a pointer expression. (Under C90, only the integer
expression is guaranteed to work; under C99, either form will do. So the
integer expression is more portable.)
 

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
474,183
Messages
2,570,967
Members
47,520
Latest member
KrisMacono

Latest Threads

Top