Returning results through the function parameter list?

D

Daniel Rudy

Hello,

Consider the following code:

/* resolve_hostname
this resolves the hostname into an ip address.
*/
static void resolve_hostname(char result[MAXSIZE], const char
hostname[MAXSIZE], const char server[MAXSIZE])
{
struct hostent *hp_ptr; /* pointer for query structure */
struct hostent hp; /* static structure in memory to hold
query results */
unsigned char *chptr; /* character pointer for address list */
struct in_addr in; /* inet_ntoa conversion structure */
unsigned int temp;

/* this is to silence the compiler on a unused variable
warning. */
strcpy(result, server);

/* network call: resolve hostname to ip address using
DNS forward lookup */
hp_ptr = gethostbyname2(hostname, AF_INET);

/* process results */
hp = *hp_ptr;
chptr = (unsigned char *)*hp.h_addr_list;
temp = *chptr;
chptr++;
temp = temp + *chptr * 256;
chptr++;
temp = temp + *chptr * 65536;
chptr++;
temp = temp + *chptr * 16777216;
in.s_addr = temp;
result = inet_ntoa(in);
if (!check_ip_addr(result)) showerror(1, verbose);
}

The problem is that the result is not returned through the parameter
result. Now if I put this statement, fprintf(stdout, "%s\n", result);,
before the closing brace, I get the result printed out, but it doesn't
return to the calling routine. Now I have done something like this
before, and I did check my old code, but I don't see what the problem
is.

Any ideas?
 
Z

zyeming

You should use:
strcpy(result, inet_ntoa(in), MAXSIZE);
instead of:
result = inet_ntoa(in);

The reason is in the manual of inet_ntoa, please notice the last
sentence:

The inet_ntoa() function converts the Internet host address in given
in network byte order to a string in standard numbers-and-dots nota-
tion. The string is returned in a statically allocated buffer, which
subsequent calls will overwrite.
 
D

Daniel Rudy

At about the time of 5/10/2005 6:11 PM, (e-mail address removed) stated the
following:
You should use:
strcpy(result, inet_ntoa(in), MAXSIZE);
instead of:
result = inet_ntoa(in);

The reason is in the manual of inet_ntoa, please notice the last
sentence:

The inet_ntoa() function converts the Internet host address in given
in network byte order to a string in standard numbers-and-dots nota-
tion. The string is returned in a statically allocated buffer, which
subsequent calls will overwrite.

Well thank you for pointing that out. It works now. One thing though,
it should probably be strncpy, not strcpy if you specify the size
parameter. It wouldn't compile using strcpy.

Now what I don't understand is why when doing result = inet_ntoa(in);
the result is available until the function exits while doing
strncpy(result, inet_ntoa(in), MAXSIZE); causes the value to be written
back to the caller.


Is it because the actual pointer value of result is pointing to that
buffer and when the function exits the reference is lost because they
are two different buffers? Just a thought.
 
K

Keith Thompson

Daniel Rudy said:
At about the time of 5/10/2005 6:11 PM, (e-mail address removed) stated the
following:
You should use:
strcpy(result, inet_ntoa(in), MAXSIZE);
instead of:
result = inet_ntoa(in);
[...]

Well thank you for pointing that out. It works now. One thing though,
it should probably be strncpy, not strcpy if you specify the size
parameter. It wouldn't compile using strcpy.
[...]

Be careful with strncpy(). In some cases, it doesn't terminate the
target string with a '\0'. In other cases, it wastefully writes
multiple '\0' characters. You may be better off using strcpy() after
first ensuring that it won't overflow.
 
Z

ZhangSafari

Well thank you for pointing that out. It works now. One thing
though,
it should probably be strncpy, not strcpy if you specify the size
parameter. It wouldn't compile using strcpy.

Oh, yes, it is my clerical error!
As Keith Thompson has pointed out, I used strncpy in the wrong way.
It should be like this if you use strncpy:
strcpy(result, inet_ntoa(in), MAXSIZE-1);
result[MAXSIZE-1] = '\0';
I'm sorry for my mistake. In fact, I'd like to use strlcpy. Because
I'm learning secure programming as my work requires. This material
shows how to use strlcpy:
http://www.courtesan.com/todd/papers/strlcpy.html
Now what I don't understand is why when doing result = inet_ntoa(in);
the result is available until the function exits while doing
strncpy(result, inet_ntoa(in), MAXSIZE); causes the value to be written
back to the caller.
Is it because the actual pointer value of result is pointing to that
buffer and when the function exits the reference is lost because they
are two different buffers? Just a thought.

The buffer is declared outside of all functions and with the specifier
static. It will be overwrited when you calls inet_ntoa() next time. SO
I think the buf will remain untill next time you call inet_ntoa().

But in your code, you just point your form parameter to that buffer, so
it will not return to the caller. That's why you can't get your result
after your resolve_hostname() returns.

At last, sorry for my bad English. Hope you will unterstand:)
 
D

Daniel Rudy

At about the time of 5/10/2005 8:17 PM, ZhangSafari stated the following:
Well thank you for pointing that out. It works now. One thing
though,

it should probably be strncpy, not strcpy if you specify the size
parameter. It wouldn't compile using strcpy.


Oh, yes, it is my clerical error!
As Keith Thompson has pointed out, I used strncpy in the wrong way.
It should be like this if you use strncpy:
strcpy(result, inet_ntoa(in), MAXSIZE-1);
result[MAXSIZE-1] = '\0';

Maybe this would be better?

strncpy(result, inet_ntoa(in), MAXSIZE - 2);
result[MAXSIZE - 1] = '\0';

You are copying one less byte.
I'm sorry for my mistake. In fact, I'd like to use strlcpy. Because
I'm learning secure programming as my work requires. This material
shows how to use strlcpy:
http://www.courtesan.com/todd/papers/strlcpy.html

Interesting. I'll have to research those new calls.
The buffer is declared outside of all functions and with the specifier
static. It will be overwrited when you calls inet_ntoa() next time. SO
I think the buf will remain untill next time you call inet_ntoa().

But in your code, you just point your form parameter to that buffer, so
it will not return to the caller. That's why you can't get your result
after your resolve_hostname() returns.

Makes sense...But I wonder what happens to that buffer when the function
exits...Better yet, what happens to that buffer when the program exits?
This could end up being a memory leak issue if the OS doesn't handle it
properly.
 
C

Chris Torek

[much editing, to leave only the nub of the problem]
static void resolve_hostname(char result[MAXSIZE], ...) { ...
result = inet_ntoa(in);

As others have already noted, the problem is that this changes
the "char *result" parameter's value, so that instead of the
value passed in from the caller, the (local) pointer now points
to the static data area that inet_ntoa() (which is not a Standard
C function, but some of us "know all about it" anyway) returns.

This sort of thing is why I prefer never to declare a formal
parameter as an array. Note that if "result" were a local array
variable (whether static or automatic), the assignment would
have been rejected:

void f(void) {
char buf[SIZE];
...
buf = some_func();

But an the array declaration appears in the position of a formal
parameter, C's rules require that the compiler "rewrite" the
declaration as if it had been a pointer instead. Hence resolve_hostname
(above) has type "void (char *, ...)".

In other words, formal parameters also undergo the transformation
specified by The Rule about arrays and pointers in C (see
<http://web.torek.net/torek/c/expr.html#therule>. This seems
natural, at least to me, once you realize that C passes all
parameters -- including arrays -- by value. The "value" of an
array is a pointer to its first element.

Note that this rewriting applies to typedef-names that specify
array types as well. Some <stdarg.h> implementations use this
property, as does <setjmp.h>. If a formal parameter has type
"array N of T", it is rewritten as if it had been declared as
a "pointer to T". For this reason, a typedef for an array type
behaves differently from all other possible typedefs in C. This
makes it essential to know whether a typedef-name names an array.
I prefer to solve this problem by avoiding typedef names :) but
another solution is just to decree that typedefs should not be
used for arrays, at least not without some darn good reason.
 
D

Daniel Rudy

At about the time of 5/11/2005 11:53 AM, Chris Torek stated the following:
[much editing, to leave only the nub of the problem]

static void resolve_hostname(char result[MAXSIZE], ...) {
...

result = inet_ntoa(in);


As others have already noted, the problem is that this changes
the "char *result" parameter's value, so that instead of the
value passed in from the caller, the (local) pointer now points
to the static data area that inet_ntoa() (which is not a Standard
C function, but some of us "know all about it" anyway) returns.

After reading a excerpt from the man page, I kinda gathered that's
probably what was going on. As for inet_ntoa() not being a standard C
function, that is kinda irrelevent at this point because this program is
designed to run in the Unix environment (FreeBSD 5.x to be specific),
but a buddy of mine who uses Linux says that it compiles and runs
correctly on his SuSE Linux 8.1 system. Don't know about his Powerbook,
as PowerPC platform is big endian while the Intel platform is little endian.

I know that is question is a little off-topic, but is there a way to
auto-determine the endianess of the build environment? I'll probably be
asking the same question in the Unix programmer group as well.
This sort of thing is why I prefer never to declare a formal
parameter as an array. Note that if "result" were a local array
variable (whether static or automatic), the assignment would
have been rejected:

void f(void) {
char buf[SIZE];
...
buf = some_func();

But an the array declaration appears in the position of a formal
parameter, C's rules require that the compiler "rewrite" the
declaration as if it had been a pointer instead. Hence resolve_hostname
(above) has type "void (char *, ...)".

In other words, formal parameters also undergo the transformation
specified by The Rule about arrays and pointers in C (see
<http://web.torek.net/torek/c/expr.html#therule>. This seems
natural, at least to me, once you realize that C passes all
parameters -- including arrays -- by value. The "value" of an
array is a pointer to its first element.

I thought that C passed parameters by reference, which is why in one of
my published programs, I had to recode the logic in a function because
when it modified the value, it went back to the calling routine.
Note that this rewriting applies to typedef-names that specify
array types as well. Some <stdarg.h> implementations use this
property, as does <setjmp.h>. If a formal parameter has type
"array N of T", it is rewritten as if it had been declared as
a "pointer to T". For this reason, a typedef for an array type
behaves differently from all other possible typedefs in C. This
makes it essential to know whether a typedef-name names an array.
I prefer to solve this problem by avoiding typedef names :) but
another solution is just to decree that typedefs should not be
used for arrays, at least not without some darn good reason.

Well, I'm still learning how to properly program under C, although I
have gained a fair bit of expertise on the subject. It will be awhile
before I start writing Windows programs, so for now, I'm sticking with Unix.
 
P

pete

Daniel Rudy wrote:
I know that is question is a little off-topic, but is there a way to
auto-determine the endianess of the build environment?

I don't know what all the different endians are.
This might help:

/* BEGIN new.c */

#include <stdio.h>

int main(void)
{
unsigned long number = 0x12345678;
unsigned byte;

printf("number is 0x%lx\n\n", number);
for (byte = 0; byte != sizeof number; ++byte) {
printf("byte %u: 0x%x\n", byte,
(unsigned)((unsigned char*)&number)[byte]);
}
return 0;
}

/* END new.c */
 
F

Flash Gordon

Daniel said:
At about the time of 5/11/2005 11:53 AM, Chris Torek stated the following:
[much editing, to leave only the nub of the problem]
static void resolve_hostname(char result[MAXSIZE], ...) {
...

result = inet_ntoa(in);

As others have already noted, the problem is that this changes
the "char *result" parameter's value, so that instead of the
value passed in from the caller, the (local) pointer now points
to the static data area that inet_ntoa() (which is not a Standard
C function, but some of us "know all about it" anyway) returns.

After reading a excerpt from the man page, I kinda gathered that's
probably what was going on. As for inet_ntoa() not being a standard C
function, that is kinda irrelevent at this point because this program is
designed to run in the Unix environment (FreeBSD 5.x to be specific),

It may be irrelevant to the level of portability you want, but it *is*
relevant to whether it is on topic here. inet_ntoa is off topic here
because we only deal with standard C here.
but a buddy of mine who uses Linux says that it compiles and runs
correctly on his SuSE Linux 8.1 system. Don't know about his Powerbook,
as PowerPC platform is big endian while the Intel platform is little endian.

I know that is question is a little off-topic, but is there a way to
auto-determine the endianess of the build environment? I'll probably be
asking the same question in the Unix programmer group as well.

This can be discussed here in part, since you can examine the
representation of numbers using standard C.

If you are concerned with endianness you are probably also concerned
with the size of the integer type (and the number of bits in a char).
CHAR_BIT will tell you how many bits in a char. The easiest thing might
be to about the build if this is not 8, at least it would be better than
compiling and not working. sizeof will obviously tell you the sizes of
the various integer types in C bytes (i.e. the number of chars, not the
number of octets) since this is not fixed. You can then store a number
(such as 0x11223344 in a 32 bit integer type) in the integer type of
your choice and use a pointer to unsigned char to examine its
representation.
This sort of thing is why I prefer never to declare a formal
parameter as an array. Note that if "result" were a local array
variable (whether static or automatic), the assignment would
have been rejected:

void f(void) {
char buf[SIZE];
...
buf = some_func();

But an the array declaration appears in the position of a formal
parameter, C's rules require that the compiler "rewrite" the
declaration as if it had been a pointer instead. Hence resolve_hostname
(above) has type "void (char *, ...)".

In other words, formal parameters also undergo the transformation
specified by The Rule about arrays and pointers in C (see
<http://web.torek.net/torek/c/expr.html#therule>. This seems
natural, at least to me, once you realize that C passes all
parameters -- including arrays -- by value. The "value" of an
array is a pointer to its first element.

I thought that C passed parameters by reference, which is why in one of
my published programs, I had to recode the logic in a function because
when it modified the value, it went back to the calling routine.

No, parameters are *always* passed by value in C.

The thing that probably confused you is that when you try to pass an
array what you actually pass is a pointer to the first element of the
array. Obviously, if you modify something the pointer points to (i.e.
the actual array) you are modifying data that is *not* necessarily local
to your function.

Since structures are passed in there entirety by value (rather than a
pointer to the struct being passed) you can wrap an array in a struct to
pass it by value.

A similar rule applies to return values, you cannot return an array
(only a pointer) but you *can* return a struct.

Well, I'm still learning how to properly program under C, although I
have gained a fair bit of expertise on the subject. It will be awhile
before I start writing Windows programs, so for now, I'm sticking with Unix.

<OT>
If you are using gcc I suggest that as a minimum you use the options
"-ansi -pedantic -Wall". Some people will suggest "-W" but I happen not
to like that.
</OT>
 
C

CBFalconer

Flash said:
.... snip ...

<OT>
If you are using gcc I suggest that as a minimum you use the
options "-ansi -pedantic -Wall". Some people will suggest "-W"
but I happen not to like that.

Not only do I recommend -W, but also -Wwrite-strings and
-Wfloat-equal. If anything shows up you have saved yourself a lot
of anguish.

--
Some informative links:
http://www.geocities.com/nnqweb/
http://www.catb.org/~esr/faqs/smart-questions.html
http://www.caliburn.nl/topposting.html
http://www.netmeister.org/news/learn2quote.html
 
L

Lawrence Kirby

At about the time of 5/10/2005 8:17 PM, ZhangSafari stated the following:
Well thank you for pointing that out. It works now. One thing
though,

it should probably be strncpy, not strcpy if you specify the size
parameter. It wouldn't compile using strcpy.


Oh, yes, it is my clerical error!
As Keith Thompson has pointed out, I used strncpy in the wrong way.
It should be like this if you use strncpy:
strcpy(result, inet_ntoa(in), MAXSIZE-1);
result[MAXSIZE-1] = '\0';

Maybe this would be better?

strncpy(result, inet_ntoa(in), MAXSIZE - 2);
result[MAXSIZE - 1] = '\0';

You are copying one less byte.

However the byte it fails to copy isn't set anywhere else, so that's a
bug when the source string is long enough. The strncpy() call sets
bytes from result[0] to result[MAXSIZE-3] inclusive, result[MAXSIZE-2] is
never set. strncpy() is a poor function for this sort of thing, it isn't a
true string handling function. strncat() is better:

result[0] = '\0';
strncat(result, inet_ntoa(in), MAXSIZE-1);

You can even do the copy in one call with sprintf()

sprintf(result, "%.*s", (int)MAXSIZE-1, inet_ntoa(in));

You may feel that sprintf() has a high overhead for such a simple
operation, and that can be true. sprintf() is good for building up complex
strings from multiple parts and it is useful to remember that it can do
things like this.

Lawrence
 
C

Chris Torek

I know that is question is a little off-topic, but is there a way to
auto-determine the endianess of the build environment?

Not portably. (On more restricted systems, rather than "everything
that supports Standard C" -- which is a LOT of machines -- there *are*
compile-time macros you can test, defined in system-specific header
files.) Note that the two most common orders today, called
little-endian and big-endian, are not the only two orders: the
PDP-11 on which C grew up used "PDP-endian" byte order for "long"s,
so that each 16-bit word was made up of 2 little-endian 8-bit bytes,
but the 32-bit "long" was made up of 2 big-endian 16-bit words.

(If C were as dominant as some hardware types seem to think it is,
perhaps many architechtures today would be PDP-endian. :) )

In many cases, you can avoid depending on hardware byte order by
writing your code so that it deals with values, rather than
representations. I have a fairly long essay on this that I should
HTML-ize and put up on the web site. The key insight, however, is
that bit and byte order occur *only* when some agent -- you, the
computer, evil aliens from Planet X, whoever -- breaks up a "whole"
into a series of "parts". Whoever breaks up the whole has to label
the individual parts, so that they can be put back together to make
the original whole. Those labels are the "order".

If you wish to have control of the order of the parts, simply do
the breaking-up and re-assembling yourself. If you let the computer
do it, you give up control of the order, and depend on the computer's
order instead.

Thus, to put out a series of 8-bit values in big-endian order
given a 32-bit value in a variable of type "unsigned long" (which
is guaranteed to hold at least 32 bits):

/*
* Output big-endian sequence. Inputs are:
*
* unsigned long value;
* FILE *fp;
*/
putc((value >> 24) & 0xff, fp);
putc((value >> 16) & 0xff, fp);
putc((value >> 8) & 0xff, fp);
putc( value & 0xff, fp);

To reassemble it:

unsigned char in[4];
/* or use 4 "int"s and check for EOF, or one int to store
the result of each getc() checking each for EOF as you
go, but here I will use feof() and ferror(). */

in[0] = getc(fp);
in[1] = getc(fp);
in[2] = getc(fp);
in[3] = getc(fp);
if (feof(fp) || ferror(fp)) ... handle error ...

value =
((in[0] & 0xff) << 24) |
((in[1] & 0xff) << 16) |
((in[2] & 0xff) << 8) |
(in[3] & 0xff);

The masking with 0xff is not necessary if "unsigned char" happens
to be exactly 8 bits, but will ensure that the code even works on
machines like the Univac 1100 series, with its 9-bit bytes.
 
C

Chris Torek

unsigned char in[4]; ...
value =
((in[0] & 0xff) << 24) |
((in[1] & 0xff) << 16) |
((in[2] & 0xff) << 8) |
(in[3] & 0xff);

The bug is that (in[0] & 0xff) has type "int" or "unsigned int",
when we want type "unsigned long" before shifting -- so these last
five lines should read:

value =
((unsigned long)(in[0] & 0xff) << 24) |
((unsigned long)(in[1] & 0xff) << 16) |
((unsigned long)(in[2] & 0xff) << 8) |
(unsigned long)(in[3] & 0xff);

The final cast is unnecessary, and the third one can use "unsigned
int", but it "looks nicer" this way and the compiler has to do the
conversion to unsigned long internally anyway.
 
D

Dave Thompson

You should use:
strcpy(result, inet_ntoa(in), MAXSIZE);
instead of:
result = inet_ntoa(in);

The reason is in the manual of inet_ntoa, please notice <snip>
The string is returned in a statically allocated buffer, <snip>

That's true, but is not the reason that the value does not appear --
even briefly -- in the caller's variable. That's because a C function
parameter declared as an array is really a pointer, which like all
parameters is passed by value, so assigning to the pointer changes
only the local copy and has no effect on anything in the caller,
whereas strcpy() stores _into the string pointed to_ as desired. See
section 6 of the FAQ at the usual places and
http://www.eskimo.com/~scs/C-faq/top.html .

- David.Thompson1 at worldnet.att.net
 

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

Forum statistics

Threads
473,769
Messages
2,569,577
Members
45,054
Latest member
LucyCarper

Latest Threads

Top