281
votes

C++ Notes: Array Initialization has a nice list over initialization of arrays. I have a

int array[100] = {-1};

expecting it to be full with -1's but its not, only first value is and the rest are 0's mixed with random values.

The code

int array[100] = {0};

works just fine and sets each element to 0.

What am I missing here.. Can't one initialize it if the value isn't zero ?

And 2: Is the default initialization (as above) faster than the usual loop through the whole array and assign a value or does it do the same thing?

13
The behaviour in C and C++ is different. In C {0} is a special case for a struct initializer, however AFAIK not for arrays. int array[100]={0} should be the same as array[100]={[0]=0}, which as a side-effect will zero all other elements. A C compiler should NOT behave as you describe above, instead int array[100]={-1} should set the first element to -1 and the rest to 0 (without noise). In C if you have a struct x array[100], using ={0} as an initializer is NOT valid. You can use {{0}} which will initialize the first element and zero all others, will in most cases will be the same thing.Fredrik Widlund
@FredrikWidlund It's the same in both languages. {0} is not a special case for structs nor arrays. The rule is that elements with no initializer get initialized as if they had 0 for an initializer. If there are nested aggregates (e.g. struct x array[100]) then initializers are applied to the non-aggregates in "row-major" order ; braces may optionally be omitted doing this. struct x array[100] = { 0 } is valid in C; and valid in C++ so long as the first member of struct X accepts 0 as initializer.M.M
{ 0 } is not special in C, but it's much harder to define a data type that can't be initialized with it since there are no constructors and thus no way to stop 0 from being implicitly converted and assigned to something.Leushenko
Voted to reopen because the other question is about C. There are many C++ ways to initialize an array that are not valid in C.xskxzr
Also voted for re-open - C and C++ are different languagesPete

13 Answers

384
votes

Using the syntax that you used,

int array[100] = {-1};

says "set the first element to -1 and the rest to 0" since all omitted elements are set to 0.

In C++, to set them all to -1, you can use something like std::fill_n (from <algorithm>):

std::fill_n(array, 100, -1);

In portable C, you have to roll your own loop. There are compiler-extensions or you can depend on implementation-defined behavior as a shortcut if that's acceptable.

140
votes

There is an extension to the gcc compiler which allows the syntax:

int array[100] = { [0 ... 99] = -1 };

This would set all of the elements to -1.

This is known as "Designated Initializers" see here for further information.

Note this isn't implemented for the gcc c++ compiler.

40
votes

The page you linked to already gave the answer to the first part:

If an explicit array size is specified, but an shorter initiliazation list is specified, the unspecified elements are set to zero.

There is no built-in way to initialize the entire array to some non-zero value.

As for which is faster, the usual rule applies: "The method that gives the compiler the most freedom is probably faster".

int array[100] = {0};

simply tells the compiler "set these 100 ints to zero", which the compiler can optimize freely.

for (int i = 0; i < 100; ++i){
  array[i] = 0;
}

is a lot more specific. It tells the compiler to create an iteration variable i, it tells it the order in which the elements should be initialized, and so on. Of course, the compiler is likely to optimize that away, but the point is that here you are overspecifying the problem, forcing the compiler to work harder to get to the same result.

Finally, if you want to set the array to a non-zero value, you should (in C++, at least) use std::fill:

std::fill(array, array+100, 42); // sets every value in the array to 42

Again, you could do the same with an array, but this is more concise, and gives the compiler more freedom. You're just saying that you want the entire array filled with the value 42. You don't say anything about in which order it should be done, or anything else.

18
votes

C++11 has another (imperfect) option:

std::array<int, 100> a;
a.fill(-1);
9
votes

With {} you assign the elements as they are declared; the rest is initialized with 0.

If there is no = {} to initalize, the content is undefined.

8
votes

The page you linked states

If an explicit array size is specified, but an shorter initiliazation list is specified, the unspecified elements are set to zero.

Speed issue: Any differences would be negligible for arrays this small. If you work with large arrays and speed is much more important than size, you can have a const array of the default values (initialized at compile time) and then memcpy them to the modifiable array.

8
votes

Using std::array, we can do this in a fairly straightforward way in C++14. It is possible to do in C++11 only, but slightly more complicated.

Our interface is a compile-time size and a default value.

template<typename T>
constexpr auto make_array_n(std::integral_constant<std::size_t, 0>, T &&) {
    return std::array<std::decay_t<T>, 0>{};
}

template<std::size_t size, typename T>
constexpr auto make_array_n(std::integral_constant<std::size_t, size>, T && value) {
    return detail::make_array_n_impl<size>(std::forward<T>(value), std::make_index_sequence<size - 1>{});
}


template<std::size_t size, typename T>
constexpr auto make_array_n(T && value) {
    return make_array_n(std::integral_constant<std::size_t, size>{}, std::forward<T>(value));
}

The third function is mainly for convenience, so the user does not have to construct a std::integral_constant<std::size_t, size> themselves, as that is a pretty wordy construction. The real work is done by one of the first two functions.

The first overload is pretty straightforward: It constructs a std::array of size 0. There is no copying necessary, we just construct it.

The second overload is a little trickier. It forwards along the value it got as the source, and it also constructs an instance of make_index_sequence and just calls some other implementation function. What does that function look like?

namespace detail {

template<std::size_t size, typename T, std::size_t... indexes>
constexpr auto make_array_n_impl(T && value, std::index_sequence<indexes...>) {
    // Use the comma operator to expand the variadic pack
    // Move the last element in if possible. Order of evaluation is well-defined
    // for aggregate initialization, so there is no risk of copy-after-move
    return std::array<std::decay_t<T>, size>{ (static_cast<void>(indexes), value)..., std::forward<T>(value) };
}

}   // namespace detail

This constructs the first size - 1 arguments by copying the value we passed in. Here, we use our variadic parameter pack indexes just as something to expand. There are size - 1 entries in that pack (as we specified in the construction of make_index_sequence), and they have values of 0, 1, 2, 3, ..., size - 2. However, we do not care about the values (so we cast it to void, to silence any compiler warnings). Parameter pack expansion expands out our code to something like this (assuming size == 4):

return std::array<std::decay_t<T>, 4>{ (static_cast<void>(0), value), (static_cast<void>(1), value), (static_cast<void>(2), value), std::forward<T>(value) };

We use those parentheses to ensure that the variadic pack expansion ... expands what we want, and also to ensure we are using the comma operator. Without the parentheses, it would look like we are passing a bunch of arguments to our array initialization, but really, we are evaluating the index, casting it to void, ignoring that void result, and then returning value, which is copied into the array.

The final argument, the one we call std::forward on, is a minor optimization. If someone passes in a temporary std::string and says "make an array of 5 of these", we would like to have 4 copies and 1 move, instead of 5 copies. The std::forward ensures that we do this.

The full code, including headers and some unit tests:

#include <array>
#include <type_traits>
#include <utility>

namespace detail {

template<std::size_t size, typename T, std::size_t... indexes>
constexpr auto make_array_n_impl(T && value, std::index_sequence<indexes...>) {
    // Use the comma operator to expand the variadic pack
    // Move the last element in if possible. Order of evaluation is well-defined
    // for aggregate initialization, so there is no risk of copy-after-move
    return std::array<std::decay_t<T>, size>{ (static_cast<void>(indexes), value)..., std::forward<T>(value) };
}

}   // namespace detail

template<typename T>
constexpr auto make_array_n(std::integral_constant<std::size_t, 0>, T &&) {
    return std::array<std::decay_t<T>, 0>{};
}

template<std::size_t size, typename T>
constexpr auto make_array_n(std::integral_constant<std::size_t, size>, T && value) {
    return detail::make_array_n_impl<size>(std::forward<T>(value), std::make_index_sequence<size - 1>{});
}

template<std::size_t size, typename T>
constexpr auto make_array_n(T && value) {
    return make_array_n(std::integral_constant<std::size_t, size>{}, std::forward<T>(value));
}



struct non_copyable {
    constexpr non_copyable() = default;
    constexpr non_copyable(non_copyable const &) = delete;
    constexpr non_copyable(non_copyable &&) = default;
};

int main() {
    constexpr auto array_n = make_array_n<6>(5);
    static_assert(std::is_same<std::decay_t<decltype(array_n)>::value_type, int>::value, "Incorrect type from make_array_n.");
    static_assert(array_n.size() == 6, "Incorrect size from make_array_n.");
    static_assert(array_n[3] == 5, "Incorrect values from make_array_n.");

    constexpr auto array_non_copyable = make_array_n<1>(non_copyable{});
    static_assert(array_non_copyable.size() == 1, "Incorrect array size of 1 for move-only types.");

    constexpr auto array_empty = make_array_n<0>(2);
    static_assert(array_empty.empty(), "Incorrect array size for empty array.");

    constexpr auto array_non_copyable_empty = make_array_n<0>(non_copyable{});
    static_assert(array_non_copyable_empty.empty(), "Incorrect array size for empty array of move-only.");
}
5
votes

Another way of initializing the array to a common value, would be to actually generate the list of elements in a series of defines:

#define DUP1( X ) ( X )
#define DUP2( X ) DUP1( X ), ( X )
#define DUP3( X ) DUP2( X ), ( X )
#define DUP4( X ) DUP3( X ), ( X )
#define DUP5( X ) DUP4( X ), ( X )
.
.
#define DUP100( X ) DUP99( X ), ( X )

#define DUPx( X, N ) DUP##N( X )
#define DUP( X, N ) DUPx( X, N )

Initializing an array to a common value can easily be done:

#define LIST_MAX 6
static unsigned char List[ LIST_MAX ]= { DUP( 123, LIST_MAX ) };

Note: DUPx introduced to enable macro substitution in parameters to DUP

4
votes

For the case of an array of single-byte elements, you can use memset to set all elements to the same value.

There's an example here.

1
votes

1) When you use an initializer, for a struct or an array like that, the unspecified values are essentially default constructed. In the case of a primitive type like ints, that means they will be zeroed. Note that this applies recursively: you could have an array of structs containing arrays and if you specify just the first field of the first struct, then all the rest will be initialized with zeros and default constructors.

2) The compiler will probably generate initializer code that is at least as good as you could do by hand. I tend to prefer to let the compiler do the initialization for me, when possible.

1
votes

In C++, it is also possible to use meta programming and variadic templates. The following post shows how to do it: Programmatically create static arrays at compile time in C++.

0
votes

In the C++ programming language V4, Stroustrup recommends using vectors or valarrays over builtin arrays. With valarrary's, when you create them, you can init them to a specific value like:

valarray <int>seven7s=(7777777,7);

To initialize an array 7 members long with "7777777".

This is a C++ way of implementing the answer using a C++ data structure instead of a "plain old C" array.

I switched to using the valarray as an attempt in my code to try to use C++'isms v. C'isms....

-2
votes

Should be a standard feature but for some reason it's not included in standard C nor C++...

#include <stdio.h>

 __asm__
 (
"    .global _arr;      "
"    .section .data;    "
"_arr: .fill 100, 1, 2; "
 );

extern char arr[];

int main() 
{
    int i;

    for(i = 0; i < 100; ++i) {
        printf("arr[%u] = %u.\n", i, arr[i]);
    }
}

In Fortran you could do:

program main
    implicit none

    byte a(100)
    data a /100*2/
    integer i

    do i = 0, 100
        print *, a(i)
    end do
end

but it does not have unsigned numbers...

Why can't C/C++ just implement it. Is it really so hard? It's so silly to have to write this manually to achieve the same result...

#include <stdio.h>
#include <stdint.h>

/* did I count it correctly? I'm not quite sure. */
uint8_t arr = {
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
};    

int main() 
{
    int i;

    for(i = 0; i < 100; ++i) {
        printf("arr[%u] = %u.\n", i, arr[i]);
    }
}

What if it was an array of 1,000,00 bytes? I'd need to write a script to write it for me, or resort to hacks with assembly/etc. This is nonsense.

It's perfectly portable, there's no reason for it not to be in the language.

Just hack it in like:

#include <stdio.h>
#include <stdint.h>

/* a byte array of 100 twos declared at compile time. */
uint8_t twos[] = {100:2};

int main()
{
    uint_fast32_t i;
    for (i = 0; i < 100; ++i) {
        printf("twos[%u] = %u.\n", i, twos[i]);
    }

    return 0;
}

One way to hack it in is via preprocessing... (Code below does not cover edge cases, but is written to quickly demonstrate what could be done.)

#!/usr/bin/perl
use warnings;
use strict;

open my $inf, "<main.c";
open my $ouf, ">out.c";

my @lines = <$inf>;

foreach my $line (@lines) {
    if ($line =~ m/({(\d+):(\d+)})/) {
        printf ("$1, $2, $3");        
        my $lnew = "{" . "$3, "x($2 - 1) . $3 . "}";
        $line =~ s/{(\d+:\d+)}/$lnew/;
        printf $ouf $line;
    } else {
        printf $ouf $line;
    }
}

close($ouf);
close($inf);