Because gcc -o barfoo foo.c bar.c (which should really be gcc -Wall -Wextra -g foo.c bar.c -o barfoo; you should always enable all warnings and debugging info when compiling your code) is compiling two compilation units (foo.c and bar.c) then linking their object files together.
Every compilation unit (or translation unit) is "self-sufficient" regarding C declarations; you should declare every -non-predefined- type (e.g. struct) you are using in a translation unit, often in some common header file.
So you should have
struct MyX {
int x;
};
and
extern struct MyX X;
in both foo.c & bar.c. To avoid copy-pasting, you probably want to put that in some myheader.h and use #include "myheader.h" at start of both foo.c and bar.c, i.e. use
// file myheader.h
#ifndef MYHEADER_INCLUDED
#define MYHEADER_INCLUDED
struct MyX {
int x;
};
extern struct MyX X;
#endif /* MYHEADER_INCLUDED */
Notice the conventional use of an include guard. Read more about the C preprocessor, e.g. documentation of GNU cpp.
Some programming languages (e.g. Ocaml, Rust, Go, ... but not C, and not yet C++) have modules or packages to deal with that issue.
PS. You should study the source code of some free software coded in C. You'll learn a lot.