According to the C Standard (6.9.2 External object definitions)
1 If the declaration of an identifier for an object has file scope and
an initializer, the declaration is an external definition for the
identifier.
2 A declaration of an identifier for an object that has file scope
without an initializer, and without a storage-class specifier or with
the storage-class specifier static, constitutes a tentative
definition. If a translation unit contains one or more tentative
definitions for an identifier, and the translation unit contains no
external definition for that identifier, then the behavior is exactly
as if the translation unit contains a file scope declaration of that
identifier, with the composite type as of the end of the translation
unit, with an initializer equal to 0.
Thus in your example these declarations of identifiers in header foo.h itself included in module foo.c
const my_struct my_struct1;
my_struct my_struct2;
are not their external definitions because they do not have initializers.
These objects are externally defined only in module foo.c
itself
const my_struct my_struct1 = { .a = 1 };
my_struct my_struct2 = { .a = 2 };
where they are explicitly initialized.
In module main.c these external declarations constitute tentative definitions and zero initialized.
According to the Appendix J
J.5.11 Multiple external definitions 1 There may be more than one
external definition for the identifier of an object, with or without
the explicit use of the keyword extern; if the definitions disagree,
or more than one is initialized, the behavior is undefined (6.9.2).
Thus the behaviour of the program is undefined unless your compiler supports the extension described in the Appendix J.
You should set specifier extern
for these identifiers in header foo.h that the declarations in main.c would not constitute tentative definitions.
The one declaration rule is applied to identifiers that have no linkage. (6.7 Declarations)
3 If an identifier has no linkage, there shall be no more than one
declaration of the identifier (in a declarator or type specifier)
with the same scope and in the same name space, except that a typedef
name can be redefined to denote the same type as it currently does and
tags may be redeclared as specified in 6.7.2.3.
In your example all identifiers have external linkage. So they may be declared several times but defined only once.