Declaring array using asterisk produces garbage

Bug reports (if you don't/won't have a Github account)
Post Reply
einar
Member
Posts: 47
Joined: Fri Sep 06, 2013 4:23 pm

Declaring array using asterisk produces garbage

Post by einar »

File "test.c":

Code: Select all

#include <stdio.h>

extern int *ptr; //extern int ptr[];

void main() {
    printf("%d, %d", ptr[0], ptr[1]);
}
File "test.asm":

Code: Select all

SECTION rodata_user

PUBLIC _ptr

_ptr:
    defw 12345
    defw 6789
Compile options:

Code: Select all

zcc +zx -vn -startup=1 -clib=new test.c test.asm -o test
appmake +zx -b test_CODE.bin -o test.tap --org 32768
Incorrect result:

Code: Select all

-11989, 11209
Using extern int ptr[] instead of extern int *ptr produces the expected result.

Tested against "z88dk-win32-20151203.zip".
einar
Member
Posts: 47
Joined: Fri Sep 06, 2013 4:23 pm

Post by einar »

Hmm... it seems the compiler is trying to apply an optimization here.

It's assuming that pointer ptr is constant when declared as extern int ptr[], so it's assigning label _prt to memory location that will store array contents.

But it's assuming that pointer prt is variable when declared as extern int *ptr, so it's assigning label _ptr to memory location that will store the pointer itself.

According to C definition, this assumption is wrong. It should be perfectly valid to declare a variable pointer as int ptr[] instead of int *ptr. It makes sense to assume a declaration like int ptr[n] to be constant, or when declared as int ptr[] = { ... }, but not when declared as just int ptr[].

If it's considered worthwhile to deviate from strict C rules, in order to improve optimizations, then it's important to document it somewhere. Both these declarations are supposed to be synonyms according to C standard, but they produce different results in z88dk when interfacing with Assembly.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

That's actually correct behaviour. There is a subtle difference between array names and pointers that is easy to forget.

In C, the array name is an alias for the address where the array is located, but a pointer has storage allocated that holds a memory address.

So:

extern int ptr[];
a = ptr[1];

should translate to:

ld hl,(_ptr + 1*sizeof(int))

notice how "_ptr" is taken as the address of the array in memory.

But this:

extern int *ptr;
a = ptr[1];

should translate to:

ld hl,(_ptr)
inc hl
inc hl ;; move to second element in array
ld e,(hl)
inc hl
ld d,(hl)

Notice that "_ptr" holds the address of the pointer's value which must be loaded first and then indexed from.

I tried a few compiles with your source code:

Code: Select all

#include <stdio.h>

extern int *ptr;

void main() {
    printf("%d, %d", ptr[0], ptr[1]);
}
zcc +zx -vn -a -clib=new test.c

Code: Select all

        ld        hl,(_ptr)
        ld        e,(hl)
        inc        hl
        ld        d,(hl)
        push        de
        ld        hl,(_ptr)
        inc        hl
        inc        hl
        call        l_gint        ;
zcc +zx -vn -a -SO3 -clib=sdcc_iy test.c

Code: Select all

        ld        hl,(_ptr)
        inc        hl
        inc        hl
        ld        c,(hl)
        inc        hl
        ld        b,(hl)
        ld        hl,(_ptr)
        ld        e,(hl)
        inc        hl
        ld        d,(hl)
Both compilers are correctly reading the ptr value and then indexing off that.

With this:

Code: Select all

#include <stdio.h>

extern int ptr[];

void main() {
    printf("%d, %d", ptr[0], ptr[1]);
}
zcc +zx -vn -a -clib=new test.c

Code: Select all

        ld        hl,(_ptr)
        push        hl
        ld        hl,(_ptr+1+1)
        push        hl
zcc +zx -vn -a -SO3 -clib=sdcc_iy test.c

Code: Select all

        ld        bc, (_ptr + 2)
        ld        de,(_ptr)
Both compilers are using "_ptr" as the start address of the array.


For:

Code: Select all

extern const int ptr[];
The correct asm for generating that array is:

Code: Select all

SECTION rodata_user

PUBLIC _ptr

_ptr:  defw 0, 1, 2, 3   // array contents
For:

Code: Select all

extern const int *ptr;
The correct asm for generating an array that is initially pointed at by "ptr" is:

Code: Select all

SECTION data_user

PUBLIC _ptr

_ptr: defw my_array

SECTION rodata_user

my_array: defw 0, 1, 2, 3   // array contents
Notice I'm also making a distinction here between rodata_user and data_user; the former holds constant non-zero values that can be placed in ROM while the latter holds non-zero values that are held in RAM and can be changed.

The "const int ptr[]" is an array of constant integers which means the array contents can be defined in ROM (ie rodata_user).
einar
Member
Posts: 47
Joined: Fri Sep 06, 2013 4:23 pm

Post by einar »

Ouch! You are right, I was mistaken. I re-checked the C standard and it works as you described. I should have thought about re-checking it first, instead of relying on my memory from reading the specs over 20 years ago... Sorry!
Post Reply