News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu
avatar_ervin

SDCC question

Started by ervin, 16:16, 11 July 15

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

ervin

Hi everyone.

I've got a little problem that's had me banging my head against a brick wall for the last 2 hours.
I've google'd it again and again, but no matter what I can't seem to make it work.
:'(

In main.c, at the top (right after my #include statements), I've got this:

typedef struct obj3d{
    i8 x;
    u8 z;
    u8 colour;
    u8 active;
} Obj3D;

Obj3D ground={0,115,0,1};


Until now I've set x, z, colour and active in code, so I could get my screen routines working without distractions.
But now I want to move away from that and use data instead to initialise ground.
Hence the initialiser list in the ground declaration.

Now, it compiles.
However, when the program runs, it appears that the initialisation didn't work, as ground appears to contain {0,0,0,0}.

Can anyone see what I'm doing wrong?

Thanks.

Carnivius

I confused.  I thought this was going to be about the San Diego Comic-Con that's currently happening.
Favorite CPC games: Count Duckula 3, Oh Mummy Returns, RoboCop Resurrection, Tankbusters Afterlife

ervin

Quote from: Carnivac on 16:23, 11 July 15
I confused.  I thought this was going to be about the San Diego Comic-Con that's currently happening.

:)
Actually the San Diego Comic Con keeps coming up in searches for SDCC, which is very annoying when I'm trying to solve a programming problem!

gerald

Quote from: ervin on 16:16, 11 July 15
Hi everyone.

I've got a little problem that's had me banging my head against a brick wall for the last 2 hours.
I've google'd it again and again, but no matter what I can't seem to make it work.
:'(

In main.c, at the top (right after my #include statements), I've got this:

typedef struct obj3d{
    i8 x;
    u8 z;
    u8 colour;
    u8 active;
} Obj3D;

Obj3D ground={0,115,0,1};


Until now I've set x, z, colour and active in code, so I could get my screen routines working without distractions.
But now I want to move away from that and use data instead to initialise ground.
Hence the initialiser list in the ground declaration.

Now, it compiles.
However, when the program runs, it appears that the initialisation didn't work, as ground appears to contain {0,0,0,0}.

Can anyone see what I'm doing wrong?

Thanks.
It look like your crt0 is not initialising the initialised global variable.
Have a look at the crt0.s that cpcitor have provided in cpc-dev-tool-chain: a portable toolchain for C/ASM development targetting CPC.

Look for the gsinit label part, which handle the global initialised variables initialisation

ronaldo

#4
@ervin: The problem is, as @gerald suggests, an initialization problem. SDCC is a compiler designed for embeded environemts, and it generally assumes that you are producing a ROM. Inside a ROM, an asignment is impossible to perform, so the initial value for a global variable must be:

       
  • Stored in the ROM
  • Copied to RAM before the program begins.
This is useful if you are actually producing a ROM, but quite perjudicial if you are producing a RAM-based program. In this second case (your case and mine) the initial value would be included in the binary stored at some place, then copied to the location of the global variable, effectively occuping 2 locations in RAM, taking double space and also taking time and code for the copy.
This is the reason why CPCtelera is using an standard crt0.s file, which does not take care of global variable initialization. To save space and time in RAM-based programs. In future versions I hope to include tools for producing ROMS (i.e. .cpr files), but now it is RAM-based.
The solution is simple: do not declare it as a variable, but as a constant:

typedef struct obj3d{
    i8 x;
    u8 z;
    u8 colour;
    u8 active;
} Obj3D;

const Obj3D ground={0,115,0,1};


Then, SDCC will include it directly in your binary, as constants don't need to be copied to RAM prior to be used, because they are not expected to change. If you need to modify it, you can do the pointer trick:

const Obj3D ground={0,115,0,1};

void main(void) {
   // Pointer is not constant, so you can modify values
   Obj3D* pGround = &ground;

   // Accessing the structure using the pointer, compiler complains with a Warning, but it lets you modify values
   pGround->x = -2;
}


Several CPCtelera examples use this trick, and you can see that all globals are either const, or have no default initializer.

If you dont like this trick, or have special requirements, you always can assign a default value using code:

Obj3D ground;

void initialize() {
   ground.x = 0;
   ground.z = 115;
   ground.colour = 0;
   ground.active = 1;
}

void main(void) {
   initialize();
   //...
}


I think I should write a help page describing this problem in the documentation. I add it to the task list :) .

FloppySoftware

I'm very surprised regarding this SDCC behaviour.

It should have an option for ram based programs, because the trick const/pointer should not have too much sense in the ANSI C world (but it works in SDCC, of course).

IMHO
floppysoftware.es < NEW URL!!!
cpm-connections.blogspot.com.es

ronaldo

@FloppySoftware: I was as surprised as you the first time I understood this behaviour. However, SDCC is not fully ANSI C compliant (it might be 90-95% more or less).

Indeed, this is a quite reasonable from the point of view of SDCC developers, as SDCC is not only for Z80 but for a great variety of processors and most of them are used in embedded systems where the program is stored in ROM. It is a little bit inconvenient when you are creating RAM-based programs, but not a hard problem actually.

There are other alternatives, like using assembler to enter initial values, but I think constants are clearer. This would be an assembler alternative:

typedef struct obj3d{
    i8 x;
    u8 z;
    u8 colour;
    u8 active;
} Obj3D;

extern Obj3D ground;

// SDCC only accepts assembly code inside a function. Remember that, in assembly,
// a function is just code (called using its address), so there is no problem in adding a dummy function
// because it would add no code to the binary. It acts as a "box" for placing code in the binary.
void dummy_function() {
__asm
_ground:: .db 0, 115, 0, 1
__endasm;
}



Optimus

Wow, thanks for that! I infact had the same problem with a global array of static variables, but never thought to use the const at the time. I was hitting my head, and then decided to initialize one after another with an init function, which takes much more memory. Even the pointer trick is nice to know, some of my global variables will be changed.

TFM

When looking at all the problem which came up here, then ... I leave my fingers off the C and stick with the Z80 assembler.  ;) :)
TFM of FutureSoft
Also visit the CPC and Plus users favorite OS: FutureOS - The Revolution on CPC6128 and 6128Plus

AMSDOS

Quote from: FloppySoftware on 19:35, 11 July 15
I'm very surprised regarding this SDCC behaviour.

It should have an option for ram based programs, because the trick const/pointer should not have too much sense in the ANSI C world (but it works in SDCC, of course).

IMHO


I was wondering, after reading about your PCW & CP/M interests, if you've used BDS C?
* Using the old Amstrad Languages :D   * with the Firmware :P
* I also like to problem solve code in BASIC :)   * And type-in Type-Ins! :D

Home Computing Weekly Programs
Popular Computing Weekly Programs
Your Computer Programs
Updated Other Program Links on Profile Page (Update April 16/15 phew!)
Programs for Turbo Pascal 3

FloppySoftware

#10
Quote from: AMSDOS on 23:39, 11 July 15

I was wondering, after reading about your PCW & CP/M interests, if you've used BDS C?

No. All my CP/M & PCW projects I have written in the C language are done with MESCC (Mike's Enhanced Small C Compiler).
floppysoftware.es < NEW URL!!!
cpm-connections.blogspot.com.es

FloppySoftware

Quote from: TFM on 23:10, 11 July 15
When looking at all the problem which came up here, then ... I leave my fingers off the C and stick with the Z80 assembler.  ;) :)

Both, C & Z80 assembler, are nice languages. I like them very much.
floppysoftware.es < NEW URL!!!
cpm-connections.blogspot.com.es

ervin

#12
Wow, thanks for all the information and discussion everyone!
This has become a very helpful thread.
My game will depend very heavily on data tables, so knowing how to do this is vital.

@gerald - thanks for the info about that file. I'm still learning my way around SDCC, so that sort of info is very useful.
@ronaldo - thanks for the detailed explanation (as usual!). Sounds like a pretty good solution.

I'll give both approaches a try tonight and see which works better.
(Or maybe they can be used together?)


ronaldo

Quote from: ervin on 01:44, 12 July 15
I'll give both approaches a try tonight and see which works better.
(Or maybe they can be used together?)
Both of them will have near to equal results. Depending on the way SDCC manages optimizations, it might be better the assembly option, but that's just speculation: just considering that specific optimizations for constants may lead to problems. However, as the compiler detects that the constant may be modified, I think it discards constant optimizations, making it equal to the assembly option.

mr_lou

I'm taking the liberty to ask another SDCC question then:

When I do this:
extern u8 myVariable = 4;
- outside all functions, then it seems to be defined but not set. I.e. the value of myVariable is not 4 but rather 0.

When I do this:
extern const u8 myVariable = 4;
- also outside all functions, then it does get defined and has the value 4.

I thought the whole "extern" keyword was supposed to work regardless of whether the variable is a constant or not?

ervin

Quote from: ronaldo on 10:34, 12 July 15
Both of them will have near to equal results. Depending on the way SDCC manages optimizations, it might be better the assembly option, but that's just speculation: just considering that specific optimizations for constants may lead to problems. However, as the compiler detects that the constant may be modified, I think it discards constant optimizations, making it equal to the assembly option.

I've decided to go with the 1st option (pGround pointing to ground).
At first this produced a compilation warning (I can't remember the wording - something to do with losing the properties of a const), but the program worked.
But after a "make clean && make" the warning went away.
;D

ronaldo

@mr_lou: extern keyword is normally used to signal that a variable or function is defined elsewhere (in another file of the same program). This lets your code use and access a variable or function which is defined and allocated in another part of your program. extern has nothing to do with initializing global variables to their default values. That works same way as we explained in previous posts.

More on extern keyword.

ronaldo

@ervin: The compilation warning will continue. It warns you that pointing to a constant with a normal pointer can lead to a change in the value of the constant (as you actually can change the value with the pointer). When you declare something constant, it's supposed to be constant (i.e. not changing), hence the warning :).

However, SDCC does some bizarre things with precompiled objects and code generation. Remember your problem with function duplication once you updated CPCtelera? I reproduced the bug and it is because of this way SDCC works. "make clean" removes the obj directory, forcing the compiler to compile all objects again, but maintains the final binary (.lib in the case of CPCtelera). You need to use "make cleanall" to ask make to delete final binary also and force SDCC to rebuild everything.

Anyway, if you know what you are doing, you can ignore the Warning, as you know that you are doing it on purpose and not by mistake.

ervin

#18
Thanks again ronaldo.
8)

I have one more question I'm afraid.
:-[

I'm hoping this will be the last question like this I must ask, and that I will then have everything I need afterwards to continue with my game.

Based on the pointer to "ground" discussed above, I have changed my "obstacle" array to something similar.


const Obj3D obstacle[4]={
    {-8,123,3,1},
    {-8,155,5,1},
    {8,187,3,1},
    {8,219,5,1},
};

Obj3D* pObstacle=&obstacle[0];


However, it doesn't seem to work.
To be honest, I don't know what it is doing, or what I now have in (for example) pObstacle[0].x.

Can anyone help?

andycadley

Personally I'd avoid the "const trick" as it's making big assumptions about what the compiler might decide to do. For example it may decide that since two pointers are supposed to the "const" value 5, it'll coalesce them into the same value. Or it might make inline assumptions that the value will never change, for example replacing a loop counter with code unrolled a fixed number of times.

That warning is there for exactly this reason. Even if SDCC doesn't make these optimizations right now, the next version just might do....

ervin

#20
Thanks Andy.
I'll give the ASM trick a try and see how I go...

[EDIT] It seems to work!
(And the obstacle table as well!)
:D :D :D

Thanks everyone for your help - now the development path is clear!
(Well, at least until the next thing I get stuck on).  :-[


TFM

Quote from: FloppySoftware on 23:58, 11 July 15
Both, C & Z80 assembler, are nice languages. I like them very much.


That's right. But Z80 is clearly defined, and it's easy to know the exact function of every command down to the bit.
That's what I miss for C. I would love to do more with Small-C on Z80 computers, but it's very hard to get complete docs. So if something doesn't work, I don't even know if I don't know how to use the compiler or if the program has an error.


That SDCC is surely a nice thing, but it doesn't run on a CPC.  :(
TFM of FutureSoft
Also visit the CPC and Plus users favorite OS: FutureOS - The Revolution on CPC6128 and 6128Plus

arnoldemu

Quote from: TFM on 15:45, 12 July 15
That SDCC is surely a nice thing, but it doesn't run on a CPC.  :(
Use Hisoft-c?

With modern C compilers it is not uncommon to find things that do not work the same between compilers. It depends on the language specification used (e.g. c++11) and what has been implemented by the compiler (e.g. compare gcc to visual studio's c compiler). Each compiler has a list of which language features are supported by each compiler version.

It is also not uncommon to crash the compiler trying to build a program, but mostly the functionality is stable and this is not often seen.

True Z80 is clearly defined. But then so is C. Small-c is different to c in many ways. SDCC is a C compiler, z88dk is a small-c compiler.

SDCC will either support C89 or C99.
ANSI C - Wikipedia, the free encyclopedia
Modern PC compiles support c++11 or c++14.

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf



C99 - Wikipedia, the free encyclopedia
My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

TFM

Quote from: arnoldemu on 17:13, 12 July 15
Use Hisoft-c?


Ah no, Small-C for CP/M Plus is way better (best I know, which runs on CPC, but always open to learn something new). Thanks for your detailed answer.


TFM of FutureSoft
Also visit the CPC and Plus users favorite OS: FutureOS - The Revolution on CPC6128 and 6128Plus

ronaldo

@ervin: You have again the same problem. Your pObstacle variable is a global variable and you are trying to use an initializer with it (&obstacle[0]). Obviously, it has the same problem as before (ROM vs RAM etc etc).

The correct way to initialize it using const values is this:

const Obj3D obstacle[4]={
    {-8,123,3,1},
    {-8,155,5,1},
    {8,187,3,1},
    {8,219,5,1}
};

Obj3D* const pObstacle=&obstacle[0];

Now pObstacle is a constant pointer to a non-constant value. So, pObstacle variable requires a initializer that will be placed in a single location.

@andycadley: I partially agree with you. In fact, the const trick has dangers with respect to the way compiler may treat constants. However, if you know what you are doing, that shouldn't be an issue. I explain myself: if you use the pointer and the constant in your code (pObstacle and obstacle, in this example) you may have problems. The compiler can (and, in fact, should) apply optimizations to obstacle (the constant) but won't apply constant optimizations to *pObstacle as it is declared non-constant. So, if you use the constant only for initialization and you always access the value through the pointer, there is no way for the compiler to apply constant optimizations, and you are safe from trouble.

Of course, you can use other alternatives such use code-initialization (assuming that you lose space and time) or assembly definition (which has it's own different set of advantages and disadvantages). If no other solution is available, I think const trick is the easiest to apply and maintain under control, under the present circumstances. However, that's just my opinion. Anyone should code according to their opinion, not mine :) .

@ervin: When using assembly for structure initialization, take into account that you should perfectly define each byte from the structure in memory. Right now, you are using 4 consecutive bytes (1 signed and 3 unsigned) which is easy to track. However, a more complex structure may easily lead to problems if you forget a single byte. Take, for instance, this structure:

typedef struct {
   u8 name[10];
   f32 time;
   u16 seed;
} TLevel;

If you wanted to use assembly for initiating values for this structure, you need to allocate exactly 10 bytes for name, 4 bytes for time and 2 bytes for seed. Moreover, you need to know how SDCC encodes floats, as you should provide bytes for time. This is an example of how this could be managed:

extern TLevel myLevels[2];

void dummy_levels() {
__asm
_myLevels::
   ;; LEVEL 0
   ;;==================================================================
   .ascii /Level 0/     ;; Level 0 name (7 bytes)
   .db 0                ;; \0 character to end the string (1 byte)
   .db 0, 0             ;; 2 more bytes to complete the 10 bytes of name[10]
   .dw #0x0000, #0x3FC0 ;; f32 time = 1.5 in IEEE 754 format (little endian, 4 bytes)
   .dw #0x0023          ;; u16 seed = 35 (initial seed for the level, 2 bytes)
   ;; LEVEL 1
   ;;==================================================================
   .ascii /2nd level/   ;; Level 1 name (9 bytes)
   .db 0                ;; \0 character to end the string (1 byte)
   .dw #0x0000, #0x41A4 ;; f32 time = 20.5 in IEEE 754 format (little endian, 4 bytes)
   .dw #0x033A          ;; u16 seed = 826 (initial seed for the level, 2 bytes)
__endasm;
}

As you see, this is not so easy and it's quite error prone. If you change the name of a level and make a mistake in the number of total bytes (say you write 'Level 13' and forget to remove a zero from the posterior 2 padding bytes) then all the values are mixed and the results are undefined. Moreover, this kind of mistake could be quite difficult to track and fix.

This is why, in general, I prefer the const trick (taking care, of course). Using it, you make normal assignments and the compiler takes care of this low-level details with no harm to performance or size.

Powered by SMFPacks Menu Editor Mod