0

I encountered a problem while testing a code.I define a macro for obtaining the number of elements of an array as follows:

#define ARRAY_SIZE(arr) sizeof(arr) / sizeof(arr[0])

This macro works fine for counting the number of elements of an array whose initializers match the storage capacity(e.g. int buf[] = {1,2,3};),but not very effective with arrays declared as : int buf[20] = {1,2,3};

Now i know counting array elements like these is pretty easy,but what about large number of elements? How do you count them? counting could be a killer,you know!

Consider the following code:

#include <stdio.h>
#include <string.h>

#define ARRAY_SIZE(arr) sizeof(arr) / sizeof(arr[0])

void g_strcat(void *_Dst, size_t dstSize, size_t bytes, const void *_Src, size_t srcSize);

int main(void)
{
    int dst[20] = { 1,2,3 };
    int src[] = { 4,5,6 };

    size_t dstSize = 3; // dstSize = ARRAY_SIZE(dst) doesn't work
    size_t srcSize = ARRAY_SIZE(src);

    g_strcat(dst, dstSize, sizeof(int), src, srcSize);

    size_t n, newSize = dstSize + srcSize;
    for (n = 0; n < newSize; n++) {
        printf("%d ", dst[n]);
    }
    putchar('\n');
    return 0;
}

void g_strcat(void *_Dst, size_t dstSize, size_t bytes, const void *_Src, size_t srcSize)
{
    memcpy((char *)_Dst + (dstSize * bytes), _Src, srcSize * bytes);
}
machine_1
  • 3,863
  • 2
  • 16
  • 39
  • do you mean to count the size of initializer list instead of size of the array? – user3528438 May 05 '16 at 13:15
  • Yes that is what i meant – machine_1 May 05 '16 at 13:15
  • So how many element do you expect `buf[20] = {1,2,3};` to have? 3 or 20? – Soren May 05 '16 at 13:27
  • @soren 3 . i actually need to count the number of initializers! – machine_1 May 05 '16 at 13:27
  • 3
    What count do you want for `buf[20] = {1,2,3,0}` ? – Peter G. May 05 '16 at 13:29
  • Then do `buf[] = {1,2,3};` – Soren May 05 '16 at 13:29
  • I don't believe you can get the size of an initializer list at run-time. The declaration `int dst[20]` caused the compiler to allocate enough space on the stack for 20 integers. The initialization list `={1,2,3} causes the compiler to emit code to put 1 in the first element, 2 in the second and 3 in the third, the rest of the elements are initialized to zero (if I recall correctly). Thus at run time the initialization list is 'lost'. – thurizas May 05 '16 at 13:30
  • @Soren How will i concatenate to the array? The array must be big enough to hold the other string – machine_1 May 05 '16 at 13:30
  • The problem is no different that `char msg[20] = "hello";` with msg being 20 bytes but the string being 5+null -- you need the equivalent of `strlen` as in @John's answer – Soren May 05 '16 at 13:37
  • 1
    This sounds a lot like an [XY problem](http://meta.stackexchange.com/a/66378). *Why* do you need to know how much of an array has been explicitly initialized? – Andrew Henle May 05 '16 at 13:43
  • @AndrewHenle Because i need to pass it to the function.My function `g_strcat()` needs to know how many elements it has to cross to make the concatenation. – machine_1 May 05 '16 at 13:45
  • @machine_1 *My function `g_strcat()` needs to know how many elements it has to cross to make the concatenation* No, the way you've defined the requirements your function needs to meet, it needs to know how many elements were *explicitly* initialized. What *problem* are you trying to solve? You're asking about why your *solution* to some unknown problem isn't working like you want it to. – Andrew Henle May 05 '16 at 15:44
  • 1
    @AndrewHenle Pretty much. I only answered it because there is a workable solution, but the problem itself seems useless in a real-world scenario. Genuinely curious why OP would need to accomplish this. – Cloud May 05 '16 at 18:25

4 Answers4

5

If you only partially initialize a list of primitive data types (ie: array of ints), the remaining elements get initialized to 0.

C99 Standard 6.7.8.21

If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate, or fewer characters in a string literal used to initialize an array of known size than there are elements in the array, the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration.

In your case, you're trying to determine the size of the initializer list. Can't really think of a valid reason to need to do this, but you could simply check when elements start being equal to zero consistently. Of course, this fails if you've deliberately set an element to zero.

The macro you wrote will work correctly (ie: return the number of elements in the array), but it will fail if you use it in a function that accepts a pointer-to-array as an argument, as pointer decay causes sizeof to act differently than one might expect.

All that being said, you can't determine the size of the initializer list in any meaningful sense, unless you do something like so, where you define the initializer list as a macro:


Code Listing


#include <stdio.h>
#define LIST    {1,2,3}

int main(void)
{
    int i[20] = LIST;
    int t[] = LIST;

    printf("elements in i: %d\n", sizeof(i)/sizeof(int));
    printf("elements in t: %d\n", sizeof(t)/sizeof(int));

    return 0;
}

Sample output


elements in i: 20
elements in t: 3

You can minimize wasted memory by putting the throw-away array into a new block scope, ie:


#include <stdio.h>
#define LIST    {1,2,3}

int main(void)
{
    int i[20] = LIST;
    int initListSize = 0;

    {
        int t[] = LIST;
        initListSize = sizeof(t) / sizeof(int);
    }

    printf("elements in t: %d\n", initListSize);

    return 0;
}

This will limit the storage lifetime of the temporary array to the narrow scope between the braces. Again, I can see this being useful maybe as an experiment, but can't see this ever finding its way into production code.

Cloud
  • 17,212
  • 12
  • 64
  • 137
  • s/pointer-to-array/pointer to array element/, a pointer to an array has the array type (although I think C compilers allow assigning pointers to arrays of different lengths.) – juanchopanza May 05 '16 at 14:00
  • @juanchopanza You are correct. I just used "pointer to array" rather than going into more detail as I didn't want to confuse OP or have to add a few more paragraphs to my (already lengthy) post. Getting into the nuances of pointer-to-type/pointer-to-array etc, is a lengthy answer itself. Thanks for pointing this out though, as it will likely help OP if he/she reads this. – Cloud May 05 '16 at 15:12
  • Excellent answer.Though i swear,i found the solution in my answer before reading yours! – machine_1 May 05 '16 at 20:01
1

Your macro is working fine. The statement:

int dst[20] = { 1,2,3 };

Creates the following in stack memory:

|1|2|3|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|

The array size is still 20 even if it has only been initialized with the first three values

Regarding your question in the comments: How will i concatenate to the array? The array must be big enough to hold the other string:

If you are working with strings (and not number arrays) the string function int len = strlen(string); can be used to test the existing usage of a string variable before concatenating.

Unlike the sizeof macro, strlen is a function that looks for the first NULL character in an array of characters:

char string[20] = {"yes"};

Creates the following in memory:

|y|e|s|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|

strlen starts at the address of string and counts characters until it encounters the first NULL. (or 0):

int len strlen(string); //will yield a len of 3

you can then use strncat(string, newStr, count); to concatenate a specified numbers of characters, count, to a string of a known capacity, thus avoiding a buffer overflow.

Keep in mind that a string variable of size == 20 for example is limited to containing a string of length == 19. The 20th position must be reserved for the NULL character.

ryyker
  • 20,437
  • 3
  • 35
  • 78
1

This will do it if we can assume that the last initialized element is not zero (because that's indistinguishable from being implicitly initialized to zero):

size_t trailing_zero_bytes(const void* data, size_t size) {
    for (; size > 0; size--) {
        if (((const char*)data)[size - 1] != 0) {
            break;
        }
    }
    return size;
}

#define ARRAY_SIZE(arr) \
    ((sizeof(arr) - trailing_zero_bytes(arr, sizeof(arr)) + sizeof(arr[0]) + 1) / sizeof(arr[0]))

If you want to count these two cases differently, you're completely out of luck (unless you parse the code using Clang or GCC-XML or whatever):

int s1[5] = { 4,5,6 }; // 2 zeros implied
int s2[5] = { 4,5,6,0 }; // 1 zero implied

Both of the above will give 3 with my approach, and there is nothing that can be done about it.

John Zwinck
  • 207,363
  • 31
  • 261
  • 371
0

If you want to determine the number of initializers of an array,declare an array to hold the initializers elements and apply the macro ARRAY_SIZE() on it.Then use memcpy() to copy the initializers to the dst[20] array.

Now you have the number of elements without hassle.

#include <stdio.h>
#include <string.h>

#define ARRAY_SIZE(arr) sizeof(arr) / sizeof(arr[0])

void g_strcat(void *_Dst, size_t dstSize, size_t bytes, const void *_Src, size_t srcSize);

int main(void)
{
    int dst[20], src[] = { 4,5,6 };
    int initializer_list[] = { 1,2,3 };

    size_t init_size = ARRAY_SIZE(initializer_list);
    memcpy(dst, initializer_list, init_size * sizeof(int));

    size_t dstSize = init_size;
    size_t srcSize = ARRAY_SIZE(src);

    g_strcat(dst, dstSize, sizeof(int), src, srcSize);
    dstSize += srcSize;

    size_t n;
    for (n = 0; n < dstSize; n++) {
        printf("%d ", dst[n]);
    }
    putchar('\n');
    return 0;
}

void g_strcat(void *_Dst, size_t dstSize, size_t bytes, const void *_Src, size_t srcSize)
{
    memcpy((char *)_Dst + (dstSize * bytes), _Src, srcSize * bytes);
}
machine_1
  • 3,863
  • 2
  • 16
  • 39