-2

I'm compiling C code with nested structs for a 3D engine, and curiously the values I'm assigning to the structures are being inverted when the program is run.

Example code:

#include <stdio.h>

struct vertexes                     // Creates a structure vertexes with 3 coordinates in space
{
    unsigned short x, y, z;         // 2 bytes variables, restricted to 0-65535
};

struct triangles                    // Creates a structure triangles
{
    struct vertexes vertex[3];      // A triangle is made of 3 vertexes, so we create p arrays of three vertexes
};


main()
{
    struct vertexes vertexTest = {1, 2, 3};
    printf("x = %d, y = %d, z = %d \n\n", vertexTest.x, vertexTest.y, vertexTest.z);

    struct triangles triangleTest = {{{1, 2, 3}, {0, 100, 0}, {100, 100, 0}}};

    for (int loop = 0; loop < 3; ++loop)
    {
        printf ("Loop %d \n", loop);
        printf("Vertex coords = %d, %d, %d \n\n", triangleTest.vertex[loop]);
    }
}

The output I get:

x = 1, y = 2, x = 3 (which is fine)

Loop 0
Vertex coords = 3, 2, 1 (here lies the problem: I've assigned 1, 2, 3!)

Loop 1
Vertex coords = 0, 100, 0 (well, you can't really invert this)

Loop 2
Vertex coords = 0, 100, 100 (again, inverted: I've assigned 100, 100, 0)

What am I doing wrong?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Olifiers
  • 3
  • 1
  • 1
    Questions seeking debugging help should generally provide a [mre] of the problem. Your posted code does not compile, because you are using a variable `loop`, but you are not defining it anywhere in your program. – Andreas Wenzel May 06 '23 at 23:59
  • 3
    Your compiler should be giving you a warning message on the line `printf("Vertex coords = %d, %d, %d \n\n", triangleTest.vertex[loop]);`, because you are using three format specifiers, but only one argument. If you are not getting a warning message, then I suggest that you increase the warning level on your compiler. You may want to read this: [Why should I always enable compiler warnings?](https://stackoverflow.com/q/57842756/12149471) – Andreas Wenzel May 07 '23 at 00:03
  • It could be instructive to replace `{ 100, 100, 0 }` with values such as `{ 0x1234, 0x5678, 0x9ABC }` (or, equivalently, `{ 4660, 22136, 39612 }` and see what is printed then. It might not help much — it might show that you had lots of zero bytes around that kept the printed value semi-sane more by accident than design. – Jonathan Leffler May 07 '23 at 01:12
  • 1
    I note that it is beneficial to post code that compiles — you have not declared the variable `loop`. Using `main()` rather than `int main()` or (better) `int main(void)` indicates that you are compiling using an antique version of C. I can't compile your code with my usual compiler options: GCC complains about the mismatch between a `struct vertexes` argument and the 3 `int` values that the format expects. When I compile with no warnings requested, I get output like `Vertex coords = 1450185268, 1450185268, 4660` for the third triangle. The value `1450185268` corresponds to `0x56701234`. – Jonathan Leffler May 07 '23 at 01:13

3 Answers3

3

The problem lies here:

        printf("Vertex coords = %d, %d, %d \n\n", triangleTest.vertex[loop]);

This does two things:

  • puts a structure of type struct vertexes on the stack
  • tells printf to pop 3 integers off the stack

This code is essentially assuming your platform puts varargs on the stack in the same order and alignment as your original structure. On a lot of platforms, the first argument is pushed last.

The behavior will be platform dependent. On my system, (Ubuntu Linux) I get the following result:

x = 1, y = 2, z = 3 

Loop 0 
Vertex coords = 131073, 131073, 131072 

Loop 1 
Vertex coords = 6553600, 6553600, 6553600 

Loop 2 
Vertex coords = 6553700, 6553700, 6553600 

i.e. just a bunch of garbage.

How do you fix this? You need to make the format string for printf, and the thing you're providing to printf match.

        printf("Vertex coords = %d, %d, %d \n\n",
            triangleTest.vertex[loop].x,
            triangleTest.vertex[loop].y,
            triangleTest.vertex[loop].z
        );

Speaking of making sure these match, do you have compiler warnings turned on? Any compiler should be able to flag when the number of arguments to printf, and the number of format specifiers don't match. When I turn on the warnings on gcc, it shows the following:

test320.c: In function ‘main’:
test320.c:24:34: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘struct vertexes’ [-Wformat=]
   24 |         printf("Vertex coords = %d, %d, %d \n\n", triangleTest.vertex[loop]);
      |                                 ~^                ~~~~~~~~~~~~~~~~~~~~~~~~~
      |                                  |                                   |
      |                                  int                                 struct vertexes
test320.c:24:38: warning: format ‘%d’ expects a matching ‘int’ argument [-Wformat=]
   24 |         printf("Vertex coords = %d, %d, %d \n\n", triangleTest.vertex[loop]);
      |                                     ~^
      |                                      |
      |                                      int
test320.c:24:42: warning: format ‘%d’ expects a matching ‘int’ argument [-Wformat=]
   24 |         printf("Vertex coords = %d, %d, %d \n\n", triangleTest.vertex[loop]);
      |                                         ~^
      |                                          |
      |                                          int

Your compiler probably has something similar.

You should really turn on warnings. They save a lot of time. I used to think, when I was learning C, that warnings didn't really matter. That changed when I spent three days debugging a program that I could've caught in three minutes had I enabled warnings.

Nick ODell
  • 15,465
  • 3
  • 32
  • 66
  • Thank you for the reply, much appreciated. If I understand correctly, the problem then lines not in the assignment of the values, but in the printf, thus this line is correct: 'struct triangles triangleTest = {{{1, 2, 3}, {0, 100, 0}, {100, 100, 0}}};' Interesting! Thanks for the explanation. By the way, I have Warnings on, z88dk doesn't seem to have picked up on that, though. – Olifiers May 07 '23 at 00:18
  • Is it platform-dependant, or undefined behaviour? I suspect the latter, but I don't have a reference for it. – pmacfarlane May 07 '23 at 00:27
  • 2
    @pmacfarlane: According to [§7.21.6.3 ¶2](http://port70.net/~nsz/c/c11/n1570.html#7.21.6.3p2) in combination with [§7.21.6.1 ¶2](http://port70.net/~nsz/c/c11/n1570.html#7.21.6.1p2), the behavior is undefined. – Andreas Wenzel May 07 '23 at 00:48
  • @Olifiers Might be worth compiling for multiple platforms. Compile with a sophisticated compiler like either gcc, clang, or MSVC, so you can get static analysis and the ability to run it on your computer, and compile with z88dk so you can run on your real platform. – Nick ODell May 07 '23 at 00:58
2

You have to explicitly print each vertex's coordinates like so:

printf("Vertex coords = %d, %d, %d \n\n",
            triangleTest.vertex[loop].x,
            triangleTest.vertex[loop].y,
            triangleTest.vertex[loop].z);
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
boreddad420
  • 187
  • 1
  • 7
  • Thanks for the reply. Appreciate this works, but I don't understand why. Why is it that triangleTest.vertex[loop].x yields the values in the right order, while triangleTest.vertex[loop] yields them backwards? This is important to understand because while the triangle only has 3 values, I will later create a struct mesh with hundreds (if not thousands) of triangles, which I will need to address procedurally without inverted values. – Olifiers May 07 '23 at 00:09
  • 1
    @Olifiers: if you invoke undefined behaviour, the results are undefined. Don't do it. That is, don't invoke undefined behaviour. You will need to identify the elements of the structure separately when calling functions like `printf()` that do not know how to process a `struct vertexes` argument. If you're passing the value to your own function that expects a `struct vertexes` argument, you won't have problems. – Jonathan Leffler May 07 '23 at 01:02
  • @Olifiers in C, structs are not special like they are in some other languages; structs are essentially become an offset of bytes when a specified size. your cpu doesn't know the internals of the struct - only that it is an offset of bytes. when you do vextexTest.x, you, in very oversimplified terms, say to the compiler, "from the start of the bytes of the declared variable vertextTest, go to x and give me the value." When you're just trying to dump the contents of the struct to stdout, your cpu doesn't know how to format the data, which will invoke undefined behavior. – boreddad420 May 07 '23 at 01:21
2

For every %d printf format specifier that you use, you must pass exactly one corresponding int argument.

In the line

printf("Vertex coords = %d, %d, %d \n\n", triangleTest.vertex[loop]);

you are violating this rule, because you are using three %d specifiers, but only one argument, which is also of the wrong type. It is of type struct vertexes, not of type int.

Due to you violating this rule, your program has undefined behavior, which means that anything can happen, including the behavior you describe in the question.

See one of the other answers for a possible explanation of why the order of the values are inverted, when you test your program. However, you cannot rely on the values being inverted, because, as mentioned above, anything may happen when invoking undefined behavior.

If you want the behavior of your program to be reliably predictable, then you should not invoke undefined behavior, by following the rule mentioned above, for example like this:

printf(
    "Vertex coords = %d, %d, %d \n\n",
    triangleTest.vertex[loop].x
    triangleTest.vertex[loop].y
    triangleTest.vertex[loop].z
);

Although these arguments are of type unsigned short instead of int, this is acceptable, because when passing an unsigned short to printf, it will get implicitly promoted to int.

However, I would recommend using %hu instead of %d for the unsigned short data type, because that way, you can use the same format specifier for both the printf and scanf family of functions and you don't have to take int promotions into account.

See the documentation for printf and scanf for further information on the format specifiers.

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39