portability problem with a function returning a struct

A

Army1987

Under Linux (gcc) this works OK, but under Windows (lcc-win32)
p.Blue always is zero.
Within the function itself, result.Blue is correct (either 0, 255,
or the floor of the expression assigned to it modulo 256, according
to the sign of n). But when returned to main(), it magically turns
to zero. (The code below is entirely copied and pasted.)
What the deuce is happening? Any ideas? sizeof(struct Pixel) is 3,
if that can matter to anything (but so it is with gcc).

#define DEBUG
#include <stdio.h>
#include <limits.h>
#include <math.h>

struct Pixel {
unsigned char Red;
unsigned char Green;
unsigned char Blue;
} colormap(int n, int max);

#ifdef DEBUG
int main(void)
{
int n, max;
while (scanf("%d%d",&n, &max) > 1) {
struct Pixel p = colormap(n, max);
printf("%d %d %d\n", (int)p.Red, (int)p.Green,
(int)p.Blue);
if (!p.Blue)
puts("p.Blue is *really* zero.");
}
return 0;
}
#else
/*real program*/
#endif

struct Pixel colormap(int n, int max)
{
struct Pixel result;
if (n < 1) { /* black if negative, white if zero */
result.Red = n ? 0 : UCHAR_MAX;
result.Green = n ? 0 : UCHAR_MAX;
result.Blue = n ? 0 : UCHAR_MAX;
} else {
const double pi = 4 * atan(1);
double val = UCHAR_MAX*(1 - log(n)/log(max))
+ log(n)/log(max);
double hue = 2 * pi * log(n) / log(2.0);
result.Red = val * (1 + cos(hue))/2;
result.Green = val * (1 + cos(hue - 2*pi/3))/2;
#ifdef DEBUG
printf("%g\t", val * (1 + cos(hue - 4*pi/3))/2);
#endif
result.Blue = val * (1 + cos(hue - 4*pi/3))/2;
}
#ifdef DEBUG
printf("result.Blue is %d\n", (int)result.Blue);
#endif
return result;
}

Behaviour with gcc:
army1987@army1987-laptop:~$ ./a.out
-1 255
result.Blue is 0
0 0 0
p.Blue is *really* zero.
0 255
result.Blue is 255
255 255 255
1 255
63.75 result.Blue is 63
255 63 63
42 255
35.4608 result.Blue is 35
9 80 35
23 32767
144.707 result.Blue is 144
0 121 144
ok thanks
army1987@army1987-laptop:~$

Behaviour with lcc-win32:
C:\lcc\projects\lcc>test
-1 255
result.Blue is 0
0 0 0
p.Blue is *really* zero.
0 255
result.Blue is 255
255 255 0
p.Blue is *really* zero.
1 255
63.75 result.Blue is 63
255 63 0
p.Blue is *really* zero.
42 255
35.4608 result.Blue is 35
9 80 0
p.Blue is *really* zero.
23 32767
144.707 result.Blue is 144
0 121 0
p.Blue is *really* zero.
go f*** yourself

C:\lcc\projects\lcc>
 
R

Richard Heathfield

Army1987 said:
Under Linux (gcc) this works OK, but under Windows (lcc-win32)
p.Blue always is zero.

I can't see any obvious issues with the code. I recommend that you try
to find the simplest possible program that exhibits the same problem,
and re-post it. For example, if you throw out all the trig, and just
set the values directly within colormap(), does the problem remain?

But this does look like a bug in the implementation. No surprise there,
given the maintainer's attitude to conformance and portability.
 
A

Army1987

Richard Heathfield said:
Army1987 said:


I can't see any obvious issues with the code. I recommend that you try
to find the simplest possible program that exhibits the same problem,
and re-post it. For example, if you throw out all the trig, and just
set the values directly within colormap(), does the problem remain?
Yes, as I showed in the original post:
struct Pixel colormap(int n, int max)
{
struct Pixel result;
if (n < 1) { /* black if negative, white if zero */
result.Red = n ? 0 : UCHAR_MAX;
result.Green = n ? 0 : UCHAR_MAX;
result.Blue = n ? 0 : UCHAR_MAX;
} else {
[snip]
#ifdef DEBUG
printf("result.Blue is %d\n", (int)result.Blue);
#endif
return result;
}

Behaviour with lcc-win32:
C:\lcc\projects\lcc>test
[snip]
0 255
result.Blue is 255
255 255 0
p.Blue is *really* zero.
But this does look like a bug in the implementation. No surprise there,
given the maintainer's attitude to conformance and portability.
I guess that, as soon as I manage to have a working Internet
connection on Linux, I will add the following to every program I'll
ever write:
struct Pixel { unsigned char Red, Green, Blue; } ****(void)
{
struct Pixel fuck_;
****.Red = ****.Green = ****.Blue = 1;
return ****;
}
int main(void) {
if (!****().Blue) system("format c: /AUTOTEST");
 
M

mark_bluemel

struct Pixel colormap(int n, int max)
{
struct Pixel result; ....
return result;

}

What happens to the storage allocated for result at the end of the
colormap function?
 
A

Army1987

Army1987 said:
struct Pixel { unsigned char Red, Green, Blue; } ****(void)
{
struct Pixel fuck_;
****.Red = ****.Green = ****.Blue = 1;
Yeah, I meant fuck_.Red = etc...
 
A

Army1987

What happens to the storage allocated for result at the end of the
colormap function?

The same which happens with any non-array automatic variable, I
guess. At least, that is what is supposed to happen.
 
M

mark_bluemel

What happens to the storage allocated for result at the end of the
colormap function?

OOPS - very bad. Forget I said it, though it's probably related in
some way. As RT suggests it's a bug with lcc-win32's method of
returning structs, and I'd guess that some compiler "magic" tries to
make the struct inside colormap() accessible to the caller.
 
R

Richard Heathfield

Army1987 said:
I guess that, as soon as I manage to have a working Internet
connection on Linux, I will add the following to every program I'll
ever write:
struct Pixel { unsigned char Red, Green, Blue; } ****(void)

Welcome to the bozo bin. See you in 30 days.
 
B

Barry

Army1987 said:
Under Linux (gcc) this works OK, but under Windows (lcc-win32)
p.Blue always is zero.
Within the function itself, result.Blue is correct (either 0, 255,
or the floor of the expression assigned to it modulo 256, according
to the sign of n). But when returned to main(), it magically turns
to zero. (The code below is entirely copied and pasted.)
What the deuce is happening? Any ideas? sizeof(struct Pixel) is 3,
if that can matter to anything (but so it is with gcc).

#define DEBUG
#include <stdio.h>
#include <limits.h>
#include <math.h>

struct Pixel {
unsigned char Red;
unsigned char Green;
unsigned char Blue;
} colormap(int n, int max);

#ifdef DEBUG
int main(void)
{
int n, max;
while (scanf("%d%d",&n, &max) > 1) {
struct Pixel p = colormap(n, max);
printf("%d %d %d\n", (int)p.Red, (int)p.Green,
(int)p.Blue);
if (!p.Blue)
puts("p.Blue is *really* zero.");
}
return 0;
}
#else
/*real program*/
#endif

struct Pixel colormap(int n, int max)
{
struct Pixel result;
if (n < 1) { /* black if negative, white if zero */
result.Red = n ? 0 : UCHAR_MAX;
result.Green = n ? 0 : UCHAR_MAX;
result.Blue = n ? 0 : UCHAR_MAX;
} else {
const double pi = 4 * atan(1);
double val = UCHAR_MAX*(1 - log(n)/log(max))
+ log(n)/log(max);
double hue = 2 * pi * log(n) / log(2.0);
result.Red = val * (1 + cos(hue))/2;
result.Green = val * (1 + cos(hue - 2*pi/3))/2;
#ifdef DEBUG
printf("%g\t", val * (1 + cos(hue - 4*pi/3))/2);
#endif
result.Blue = val * (1 + cos(hue - 4*pi/3))/2;
}
#ifdef DEBUG
printf("result.Blue is %d\n", (int)result.Blue);
#endif
return result;
}

Just an open question of style. Would most of you write
this as is or make colormap take the struct as an argument?

e.g.

colormap(struct Pixel p, int n, int max);
or
colormap(struct Pixel *p, int n, int max);

In most cases I tend to use the last form.
 
B

Barry

Barry said:
Just an open question of style. Would most of you write
this as is or make colormap take the struct as an argument?

e.g.

colormap(struct Pixel p, int n, int max);

Of course you can scratch this one since it won't work.
Too early in the morning to be commenting about code.
 
R

Richard Tobin

Just an open question of style. Would most of you write
this as is or make colormap take the struct as an argument?

e.g.

colormap(struct Pixel p, int n, int max);
[/QUOTE]
Of course you can scratch this one since it won't work.
True.

The natural thing would be to return the struct, just as it would be
for an int or any other scalar type. The main reason *not* to return
a struct is to avoid copying, and for a small struct that is likely to
be negligible.

(Incidentally, a common way to implement struct return is for the
compiler to allocate space in the caller's stack frame, and pass a
pointer to it. So when the result is assigned to a struct variable it
might be possible for the compiler to optimise away the copy, and just
pass in the address of the destination variable. Or it might also be
able to use the passed-in struct directly in the called function
instead of allocating a variable there. Or even both, though it would
have to consider the possibility of the assigned-to struct being
aliased, and perhaps other things.)

-- Richard
 
B

Barry

Of course you can scratch this one since it won't work.
True.

The natural thing would be to return the struct, just as it would be
for an int or any other scalar type. The main reason *not* to return
a struct is to avoid copying, and for a small struct that is likely to
be negligible.

(Incidentally, a common way to implement struct return is for the
compiler to allocate space in the caller's stack frame, and pass a
pointer to it. So when the result is assigned to a struct variable it
might be possible for the compiler to optimise away the copy, and just
pass in the address of the destination variable. Or it might also be
able to use the passed-in struct directly in the called function
instead of allocating a variable there. Or even both, though it would
have to consider the possibility of the assigned-to struct being
aliased, and perhaps other things.)
[/QUOTE]

It may well turn out I am in the minority opinion of style.
I think it confuses the issue of functions which malloc() a struct.

< OT seperate>
You have to keep in mind there are many C compilers that don't
support struct assignment at all.
</OT>

But my opinion is you already have the object. Why copy it as oppose
to pass the pointer?

< MORE OT I find it particularly amusing that lcc is broken. >
 
C

Clark Cox

It may well turn out I am in the minority opinion of style.
I think it confuses the issue of functions which malloc() a struct.

< OT seperate>
You have to keep in mind there are many C compilers that don't
support struct assignment at all.
</OT>

Then they're not C compilers.
 
K

Keith Thompson

Barry said:
< OT seperate>
You have to keep in mind there are many C compilers that don't
support struct assignment at all.
</OT>
[...]

Can you provide an example? Struct assignment is required by both C90
and C99, and I think it was fairly common before the ANSI C89 standard
was published.
 
C

Chris Dollin

Barry said:
< OT seperate>
You have to keep in mind there are many C compilers that don't
support struct assignment at all.
</OT>

Name one.

A /recent/ one; let's say something after 1989.

(Such a compiler cannot, of course, claim conformance to
either C standard. I can believe that there could be
circumstances in which C-without-struct-assignments
would be good enough for some application.)
 
I

Ian Collins

Of course you can scratch this one since it won't work.
True.

The natural thing would be to return the struct, just as it would be
for an int or any other scalar type. The main reason *not* to return
a struct is to avoid copying, and for a small struct that is likely to
be negligible.

(Incidentally, a common way to implement struct return is for the
compiler to allocate space in the caller's stack frame, and pass a
pointer to it. So when the result is assigned to a struct variable it
might be possible for the compiler to optimise away the copy, and just
pass in the address of the destination variable. Or it might also be
able to use the passed-in struct directly in the called function
instead of allocating a variable there. Or even both, though it would
have to consider the possibility of the assigned-to struct being
aliased, and perhaps other things.)
[/QUOTE]
This technique of return value optimisation (RVO) is a well known and
often discussed optimisation in the C++ world, so I assume it is common
in modern C compilers.
 
R

Richard Tobin

Barry said:
< OT seperate>
You have to keep in mind there are many C compilers that don't
support struct assignment at all.
</OT>

Fortunately I don't.

I suppose there may still be C compilers in use that don't support it
(as well as some with bugs), but I don't have to worry about them.
If someone needs to port my code to them, it will be their problem,
and a good reason for them to find another compiler.
But my opinion is you already have the object. Why copy it as oppose
to pass the pointer?

You could say the same for an int, or a double.

-- Richard
 

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,767
Messages
2,569,572
Members
45,046
Latest member
Gavizuho

Latest Threads

Top