Błąd „element inicjujący nie jest stały” podczas próby zainicjowania zmiennej za pomocą const


186

W wierszu 6 pojawia się błąd (inicjowanie my_foo do foo_init) następującego programu i nie jestem pewien, czy rozumiem dlaczego.

typedef struct foo_t {
    int a, b, c;
} foo_t;

const foo_t foo_init = { 1, 2, 3 };
foo_t my_foo = foo_init;

int main()
{
    return 0;
}

Pamiętaj, że jest to uproszczona wersja większego, złożonego z wielu plików projektu, nad którym pracuję. Celem było posiadanie jednej stałej w pliku obiektowym, której wiele plików mogłoby użyć do zainicjowania struktury stanu. Ponieważ jest to osadzony cel z ograniczonymi zasobami, a struktura nie jest tak mała, nie chcę wielu kopii źródła. Wolałbym nie używać:

#define foo_init { 1, 2, 3 }

Próbuję również napisać kod przenośny, więc potrzebuję rozwiązania, które jest prawidłowe C89 lub C99.

Czy ma to związek z ORG w pliku obiektowym? Czy zainicjowane zmienne wchodzą w jeden ORG i są inicjowane poprzez skopiowanie zawartości drugiego ORG?

Może po prostu będę musiał zmienić moją taktykę i mieć funkcję inicjującą, która wykona wszystkie kopie podczas uruchamiania. Chyba że istnieją inne pomysły?

Odpowiedzi:


269

W języku C obiekty o statycznym czasie przechowywania muszą być inicjowane przy użyciu stałych wyrażeń lub agregujących inicjatorów zawierających stałe wyrażenia.

„Duży” obiekt nigdy nie jest stałym wyrażeniem w C, nawet jeśli obiekt jest zadeklarowany jako const.

Ponadto, w języku C, określenie „stały” odnosi się do dosłownych stałych (takich jak 1, 'a', 0xFFi tak dalej), członków enum i wynikach takich jak operatorzy sizeof. Obiekty o stałej jakości (dowolnego typu) niestałymi w terminologii języka C. Nie można ich używać w inicjalizatorach obiektów o statycznym czasie przechowywania, niezależnie od ich typu.

Na przykład NIE jest to stała

const int N = 5; /* `N` is not a constant in C */

Powyższe Nbyłoby stałą w C ++, ale nie jest stałą w C. Więc jeśli spróbujesz to zrobić

static int j = N; /* ERROR */

dostaniesz ten sam błąd: próba zainicjowania obiektu statycznego niestałością.

To jest powód, dla którego w języku C używamy głównie #definedo deklarowania nazwanych stałych, a także #definedo tworzenia nazwanych inicjatorów agregujących.


2
+5 za miłe wyjaśnienie, ale, co zaskakujące, ten program dobrze się kompiluje na ideone: ideone.com/lx4Xed . Czy to błąd kompilatora czy rozszerzenie kompilatora? Dzięki
Destructor,

2
@meet: Nie wiem, jakiej kombinacji opcji kompilatora używa ideone pod maską, ale ich wyniki są często dziwne poza opisem. Próbowałem skompilować ten kod na Coliru ( coliru.stacked-crooked.com/a/daae3ce4035f5c8b ) i dostałem oczekiwany błąd bez względu na to, jakiego ustawienia dialektu języka C użyłem. Nie widzę nic takiego wymienionego na stronie internetowej GCC jako rozszerzenia języka C. Innymi słowy, nie mam pojęcia, jak i dlaczego kompiluje się w ideone. Nawet jeśli kompiluje się jako rozszerzenie języka, nadal powinien generować komunikat diagnostyczny w C.
AnT

15
enum { N = 5 };jest niedocenianym sposobem deklarowania stałych bez konieczności uciekania się #define.
MM

2
@PravasiMeet „ideone” po prostu nie wyświetla wielu komunikatów diagnostycznych generowanych przez kompilator, więc nie jest to zbyt dobra strona do określania, czy kod jest poprawny, czy nie.
MM

1
Odkryłem coś interesującego. jeśli ptr jest statycznym wskaźnikiem zdefiniowanym w funkcji, to jest to błąd: static int* ptr = malloc(sizeof(int)*5);ale to NIE jest błąd static int* ptr; ptr = malloc(sizeof(int)*5);:: D
aderchox

74

To ograniczenie języka. W sekcji 6.7.8 / 4:

Wszystkie wyrażenia w inicjalizatorze dla obiektu o statycznym czasie przechowywania powinny być wyrażeniami stałymi lub literałami ciągów.

W sekcji 6.6 specyfikacja określa, co należy uznać za wyrażenie stałe. Nigdzie nie wskazuje, że zmienna const musi być uważana za wyrażenie stałe. Kompilator może rozszerzyć tę funkcję ( 6.6/10 - An implementation may accept other forms of constant expressions), ale ograniczy to przenośność.

Jeśli możesz to zmienić, my_fooaby nie miało pamięci statycznej, wszystko będzie dobrze:

int main()
{
    foo_t my_foo = foo_init;
    return 0;
}

Podoba mi się, że zacytowałeś specyfikację, ale to nie pomaga mi zrozumieć, co mamy robić ani dlaczego rzeczy są takie, jakie są.
Evan Carroll

1
Wygląda na to, że GCC 8.1 (i nowsze) zaimplementowało pewne rozszerzenie, jak opisano w tej odpowiedzi; akceptuje static const int x = 3; static int y = x;.
Eric Postpischil

5

Tylko dla ilustracji przez porównanie i kontrast Kod pochodzi z http://www.geeksforgeeks.org/g-fact-80/ / Kod kończy się niepowodzeniem w gcc i przechodzi w g ++ /

#include<stdio.h>
int initializer(void)
{
    return 50;
}

int main()
{
    int j;
    for (j=0;j<10;j++)
    {
        static int i = initializer();
        /*The variable i is only initialized to one*/
        printf(" value of i = %d ", i);
        i++;
    }
    return 0;
}

2

To jest trochę stare, ale napotkałem podobny problem. Możesz to zrobić, jeśli używasz wskaźnika:

#include <stdio.h>
typedef struct foo_t  {
    int a; int b; int c;
} foo_t;
static const foo_t s_FooInit = { .a=1, .b=2, .c=3 };
// or a pointer
static const foo_t *const s_pFooInit = (&(const foo_t){ .a=2, .b=4, .c=6 });
int main (int argc, char **argv) {
    const foo_t *const f1 = &s_FooInit;
    const foo_t *const f2 = s_pFooInit;
    printf("Foo1 = %d, %d, %d\n", f1->a, f1->b, f1->c);
    printf("Foo2 = %d, %d, %d\n", f2->a, f2->b, f2->c);
    return 0;
}

5
Nie widzę tutaj zmiennej o statycznym czasie przechowywania, która jest inicjowana przez zmienną niestałą.
Do widzenia,

0

gcc 7.4.0 nie może kompilować kodów jak poniżej:

#include <stdio.h>
const char * const str1 = "str1";
const char * str2 = str1;
int main() {
    printf("%s - %s\n", str1, str2);
    return 0;
}

constchar.c: 3: 21: error: element inicjujący nie jest stały const char * str2 = str1;

W rzeczywistości ciąg „const char *” nie jest stałą czasową kompilacji, więc nie może być inicjatorem. Ale ciąg „const char * const” jest stałą czasową kompilacji, powinien być w stanie być inicjatorem. Myślę, że to niewielka wada CLanga.

Nazwa funkcji jest oczywiście stałą czasową kompilacji, więc ten kod działa:

void func(void)
{
    printf("func\n");
}
typedef void (*func_type)(void);
func_type f = func;
int main() {
    f();
    return 0;
}

W opublikowanym kodzie str1nie ma wyrażenia według 6.7.9 Inicjalizacja , akapit 4 : „Wszystkie wyrażenia w inicjalizatorze dla obiektu, który ma czas przechowywania statyczny lub wątkowy, powinny być wyrażeniami stałymi lub literałami ciągów”.
Andrew Henle
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.