Languages like Java and C++ were developed out of C to support (i.a.) better encapsulation and information hiding. C only provides very basic support, and otherwise expects enough discipline from the programmer to avoid breaking any intended encapsulation.

There are no classes and no packages in C, so there is nothing for C functions to belong to, and they must be carefully named to avoid clashes. C structures encapulate several data as a unit, but do not restrict access, so there is no abstraction.

The use of header files and separate modules can provide some hiding of internals. An empty structure type might be used in the published header, while its full declaration would appear only in the source file that needed it, or in an unpublished header if needed by several. Functions and globals local to a module can be hidden from other modules with static.

Here's a fairly robust template for abstract types in C. First, write a header declaring (say) a type for a handle to access a very simple database:

/* file db.h */
#ifndef db_included
#define db_included

/* pointer to incomplete structure type */
typedef struct db_handle *db_ref;

/* a constructor */
db_ref db_open(const char *addr);

/* methods */
int db_get(db_ref, const char *key);
/* etc */

/* a destructor */
void db_close(db_ref);

#endif

Note that C does not have any notion of ‘constructor’ or ‘method’. They are just ordinary functions alike.

Now write a source file to complete the structure type, and define the functions:

/* file db.c */

/* Include the header, so we ensure that our definitions
   and declarations are consistent. */
#include "db.h"

struct db_handle {
  /* . . . */
};

/* Use static for internal state and private functions... */
static void normalize_key(char *to, const char *from)
{
  /* . . . */
}

db_ref db_open(const char *addr)
{
  /* Allocate a struct db_handle, and initialize it... */
}

int db_get(db_ref db, const char *key)
{
  /* Use info in db to access an entry... */
}

void db_close(db_ref)
{
  /* Release memory... */
}

As a result, the internal structure of your database handle can change over time without affecting its users, as they can't see inside it. The functions declared with static are not visible outside db.c, so they won't clash with identically named functions in other parts of the program. However, the compiler will not force a user of your library to initialize a db_ref correctly with db_open, or to release it after use correctly with db_close. He is expected to have enough self-discipline to do that himself.