56

I apologize if this is a subjective or repeated question. It's sort of awkward to search for, so I wasn't sure what terms to include.

What I'd like to know is what the basic foundation tools/functions are in C when you don't include standard libraries like stdio and stdlib.

What could I do if there's no printf(), fopen(), etc?

Also, are those libraries technically part of the "C" language, or are they just very useful and effectively essential libraries?

Chris Cooper
  • 17,276
  • 9
  • 52
  • 70

11 Answers11

31

The C standard has this to say (5.1.2.3/5):

The least requirements on a conforming implementation are:

— At sequence points, volatile objects are stable in the sense that previous accesses are complete and subsequent accesses have not yet occurred.

— At program termination, all data written into files shall be identical to the result that execution of the program according to the abstract semantics would have produced.

— The input and output dynamics of interactive devices shall take place as specified in 7.19.3.

So, without the standard library functions, the only behavior that a program is guaranteed to have, relates to the values of volatile objects, because you can't use any of the guaranteed file access or "interactive devices". "Pure C" only provides interaction via standard library functions.

Pure C isn't the whole story, though, since your hardware could have certain addresses which do certain things when read or written (whether that be a SATA or PCI bus, raw video memory, a serial port, something to go beep, or a flashing LED). So, knowing something about your hardware, you can do a whole lot writing in C without using standard library functions. Potentially, you could implement the C standard library, although this might require access to special CPU instructions as well as special memory addresses.

But in pure C, with no extensions, and the standard library functions removed, you basically can't do anything other than read the command line arguments, do some work, and return a status code from main. That's not to be sniffed at, it's still Turing complete subject to resource limits, although your only resource is automatic and static variables, no heap allocation. It's not a very rich programming environment.

The standard libraries are part of the C language specification, but in any language there does tend to be a line drawn between the language "as such", and the libraries. It's a conceptual difference, but ultimately not a very important one in principle, because the standard says they come together. Anyone doing something non-standard could just as easily remove language features as libraries. Either way, the result is not a conforming implementation of C.

Note that a "freestanding" implementation of C only has to implement a subset of standard includes not including any of the I/O, so you're in the position I described above, of relying on hardware-specific extensions to get anything interesting done. If you want to draw a distinction between the "core language" and "the libraries" based on the standard, then that might be a good place to draw the line.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • On hardware with memory-mapped I/O, you can use it via `volatile uint32_t *` or whatever; that's part of what `volatile` is for. Although yes you'd often need barrier instructions or other things that aren't part of pure C, and would be exposed via extensions in normal C implementations. – Peter Cordes Oct 16 '19 at 22:52
26

What could I do if there's no printf(), fopen(), etc?

As long as you know how to interface the system you are using you can live without the standard C library. In embedded systems where you only have several kilobytes of memory, you probably don't want to use the standard library at all.

Here is a Hello World! example on Linux and Windows without using any standard C functions:

For example on Linux you can invoke the Linux system calls directly in inline assembly:

/* 64 bit linux. */

#define SYSCALL_EXIT 60
#define SYSCALL_WRITE 1

void sys_exit(int error_code)
{
    asm volatile
    (
        "syscall"
        : 
        : "a"(SYSCALL_EXIT), "D"(error_code)
        : "rcx", "r11", "memory"
    );
}

int sys_write(unsigned fd, const char *buf, unsigned count)
{
    unsigned ret;

    asm volatile
    (
        "syscall"
        : "=a"(ret)
        : "a"(SYSCALL_WRITE), "D"(fd), "S"(buf), "d"(count)
        : "rcx", "r11", "memory"
    );
    
    return ret;
}

void _start(void)
{
    const char hwText[] = "Hello world!\n";

    sys_write(1, hwText, sizeof(hwText));
    sys_exit(12);
}

You can look up the manual page for "syscall" which you can find how can you make system calls. On Intel x86_64 you put the system call id into RAX, and then return value will be stored in RAX. The arguments must be put into RDI, RSI, RDX, R10, R9 and R8 in this order (when the argument is used).

Once you have this you should look up how to write inline assembly in gcc. The syscall instruction changes the RCX, R11 registers and memory so we add this to the clobber list make GCC aware of it.

The default entry point for the GNU linker is _start. Normally the standard library provides it, but without it you need to provide it. It isn't really a function as there is no caller function to return to. So we must make another system call to exit our process.

Compile this with:

gcc -nostdlib nostd.c 

And it outputs Hello world!, and exits.

On Windows the system calls are not published, instead it's hidden behind another layer of abstraction, the kernel32.dll. Which is always loaded when your program starts whether you want it or not. So you can simply include windows.h from the Windows SDK and use the Win32 API as usual:

#include <windows.h>

void _start(void)
{
    const char str[] = "Hello world!\n";
    HANDLE stdout = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD written;

    WriteFile(stdout, str, sizeof(str), &written, NULL);
    ExitProcess(12);
}

The windows.h has nothing to do with the standard C library, as you should be able to write Windows programs in any other language too.

You can compile it using the MinGW tools like this:

gcc -nostdlib C:\Windows\System32\kernel32.dll nostdlib.c

Then the compiler is smart enough to resolve the import dependencies and compile your program.

If you disassemble the program, you can see only your code is there, there is no standard library bloat in it.

So you can use C without the standard library.

Calmarius
  • 18,570
  • 18
  • 110
  • 157
14

What could you do? Everything!

There is no magic in C, except perhaps the preprocessor.

The hardest, perhaps is to write putchar - as that is platform dependent I/O.

It's a good undergrad exercise to create your own version of varargs and once you've got that, do your own version of vaprintf, then printf and sprintf.

I did all of then on a Macintosh in 1986 when I wasn't happy with the stdio routines that were provided with Lightspeed C - wrote my own window handler with win_putchar, win_printf, in_getchar, and win_scanf.

This whole process is called bootstrapping and it can be one of the most gratifying experiences in coding - working with a basic design that makes a fair amount of practical sense.

Rich Apodaca
  • 28,316
  • 16
  • 103
  • 129
plinth
  • 48,267
  • 11
  • 78
  • 120
  • 1
    Yes, and you can't write putchar in C. – WhirlWind Apr 03 '10 at 23:19
  • write(3) is the first syscall you need :) @whirlwind int win_putchar(char c) { return write(1, &c, 1); } – dzen Apr 03 '10 at 23:26
  • Sorry, write(3) isn't a syscall; it's a standard libray function. sys_write is the system call in most cases, and good luck calling that directly without assembly or the syscall() library function. – WhirlWind Apr 03 '10 at 23:27
  • 12
    The hell you can't. putchar takes a character and writes it out - where? Well, that's an implementation detail. On an embedded system, it might be a serial port, so that putchar looks something like void putchar(char c) { *addressOfOutPort = c; } On the Macintosh code I wrote, it was bit-blitting the character I wanted into the window at the appropriate location and scrolling. – plinth Apr 03 '10 at 23:27
  • 1
    putchar() requires assembly to write, not C. It has to call into the OS, and any time you call into the OS, you go through assembly code. Yeah, on certain systems that lack a protective OS, you might be able to do it all in C as I mentioned in my post. – WhirlWind Apr 03 '10 at 23:29
  • 9
    @WhirlWind - By casting `char *` data to a function pointer, you can embed and execute arbitrary assembly code from C on essentially any system. – Chris Lutz Apr 03 '10 at 23:39
  • dzen -- yeah, it's a common misconception. It's a library call that maps fairly directly to the sys_write system call. If you look at the source, you'll find that write is defined in the standard libraries by making a syscall to sys_write. There's not much difference, but unless you are using assembly, you don't ever call directly into the OS. In fact, you can't, because of the user-kernel stack transition. – WhirlWind Apr 03 '10 at 23:41
  • Chris sure enough... I suppose that's a bit of a perversion of C, but then again, that's the sort of stuff C is for. I didn't think of that. – WhirlWind Apr 03 '10 at 23:43
  • Chris, can you explain your method a bit more or at least give some helpful sites? thanks.. – kolistivra Apr 12 '10 at 14:25
  • 4
    Oh, stop speaking in hypotheticals -- it's easy to check! :-) On Google code search, enter "putchar" -- the first result is GNU libc's putchar(), which is written in C. It calls `_IO_putc_unlocked` which is a macro that does some pointer arithmetic on an internal struct. – Ken Jun 05 '10 at 23:33
  • @ChrisLutz: That's undefined behaviour, though. And in many real implementations, you need to sync the cache before you can exec newly-written data as code. Even on x86, gcc's optimizer will break your code [if you don't use `__builtin___clear_cache`](https://stackoverflow.com/questions/35741814/how-does-builtin-clear-cache-work#comment85964322_35741869) which basically tells it that the stores aren't "dead". – Peter Cordes Oct 16 '19 at 22:53
8

You're certainly not obligated to use the standard libraries if you have no need for them. Quite a few embedded systems either have no standard library support or can't use it for one reason or another. The standard even specifically talks about implementations with no library support, C99 standard 5.1.2.1 "Freestanding environment":

In a freestanding environment (in which C program execution may take place without any benefit of an operating system), the name and type of the function called at program startup are implementation-defined. Any library facilities available to a freestanding program, other than the minimal set required by clause 4, are implementation-defined.

The headers required by C99 to be available in a freestanding implemenation are <float.h>, <iso646.h>, <limits.h>, <stdarg.h>, <stdbool.h>, <stddef.h>, and <stdint.h>. These headers define only types and macros so there's no need for a function library to support them.

Without the standard library, you're entire reliant on your own code, any non-standard libraries that might be available to you, and any operating system system calls that you might be able to interface to (which might be considered non-standard library calls). Quite possibly you'd have to have your C program call assembly routines to interface to devices and/or whatever operating system might be on the platform.

Michael Burr
  • 333,147
  • 50
  • 533
  • 760
3

You can't do a lot, since most of the standard library functions rely on system calls; you are limited to what you can do with the built-in C keywords and operators. It also depends on the system; in some systems you may be able to manipulate bits in a way that results in some external functionality, but this is likely to be the exception rather than the rule.

C's elegance is in it's simplicity, however. Unlike Fortran, which includes much functionality as part of the language, C is quite dependent on its library. This gives it a great degree of flexibility, at the expense of being somewhat less consistent from platform to platform.

This works well, for example, in the operating system, where completely separate "libraries" are implemented, to provide similar functionality with an implementation inside the kernel itself.

Some parts of the libraries are specified as part of ANSI C; they are part of the language, I suppose, but not at its core.

WhirlWind
  • 13,974
  • 3
  • 42
  • 42
3

None of them is part of the language keywords. However, all C distributions must include an implementation of these libraries. This ensures portability of many programs.

First of all, you could theoretically implement all these functions yourself using a combination of C and assembly, so you could theoretically do anything.

In practical terms, library functions are primarily meant to save you the work of reinventing the wheel. Some things (like string and library functions) are easier to implement. Other things (like I/O) very much depend on the operating system. Writing your own version would be possible for one O/S, but it is going to make the program less portable.

But you could write programs that do a lot of useful things (e.g., calculate PI or the meaning of life, or simulate an automata). Unless you directly used the OS for I/O, however, it would be very hard to observe what the output is.

In day to day programming, the success of a programming language typically necessitates the availability of a useful high-quality standard library and libraries for many useful tasks. These can be first-party or third-party, but they have to be there.

Uri
  • 88,451
  • 51
  • 221
  • 321
3

The std libraries are "standard" libraries, in that for a C compiler to be compliant to a standard (e.g. C99), these libraries must be "include-able." For an interesting example that might help in understanding what this means, have a look at Jessica McKellar's challenge here:

http://blog.ksplice.com/2010/03/libc-free-world/

Edit: The above link has died (thanks Oracle...) I think this link mirrors the article: https://sudonull.com/post/178679-Hello-from-the-libc-free-world-Part-1

Rooke
  • 2,013
  • 3
  • 22
  • 34
  • I don't think this answered the question. It just explained adjacent topics to the actual query. – Mark Sep 19 '22 at 12:41
0

The CRT is part of the C language just as much as the keywords and the syntax. If you are using C, your compiler MUST provide an implementation for your target platform.

Edit: It's the same as the STL for C++. All languages have a standard library. Maybe assembler as the exception, or some other seriously low level languages. But most medium/high levels have standard libs.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • 2
    You might want to look at the difference between a "hosted" implementation (which requires the full standard library) and a "freestanding" implementation, which requires only a few libraries. – David Thornley Apr 03 '10 at 23:35
0

The Standard C Library is part of ANSI C89/ISO C90. I've recently been working on the library for a C compiler that previously was not ANSI-compliant.

The book The Standard C Library by P.J. Plauger was a great reference for that project. In addition to spelling out the requirements of the standard, Plauger explains the history of each .h file and the reasons behind some of the API design. He also provides a full implementation of the library, something that helped me greatly when something in the standard wasn't clear.

The standard describes the macros, types and functions for each of 15 header files (include stdio.h, stdlib.h, but also float.h, limits.h, math.h, locale.h and more).

A compiler can't claim to be ANSI C unless it includes the standard library.

tomlogic
  • 11,489
  • 3
  • 33
  • 59
  • It could be a free-standing implementation without the library (just the headers of macros) - it can't be a host implementation, though, without the library. – Jonathan Leffler Apr 03 '10 at 23:58
  • @Jonathan: Correct, he does explain where the library has to interface to the host (stdio, signals, exit, etc.). Much of the standard library is independent of the host platform, but there are definitely parts of it that require an interface to the host. – tomlogic Apr 04 '10 at 01:29
  • A compiler is not a library. It can either compile standard C, or it can not. While its nice to get a standard C library _with_ a compiler, its not a requirement in either direction. – Tim Post Apr 04 '10 at 04:46
  • 1
    @Tim: I believe that the C Standard only recognizes an implementation, which must include a compiler and a library (for a hosted implementation). While you may be able to separate the two - witness GCC and glibc - the standard would only recognize the compiler plus a supporting library as the implementation. – Jonathan Leffler Apr 04 '10 at 05:09
  • @Jonathan - My curiosity lies in the fact that gcc works just find with other C libraries (uclibc, dietlibc, etc), and gilbc, dietlibc and uclibc compile just fine with compilers that are not gcc. I always interpreted that to mean, a C compiler should be able to compile a standard C library (not vouching for any of the above being standard, however, though I think diet is). – Tim Post Apr 04 '10 at 05:37
  • @Tim: yes, GCC works with multiple libraries - for example, on Solaris, it uses the local C library. Actually, as it builds itself, it provides some fixes for what it considers to be bust headers - for various reasons. C is relatively easy - there is a reliable ABI for each platform which the compilers know how to conform to. GCC also has built-ins for some functions - the string and memory functions, in particular - so it doesn't use the native library for those (unless you tell it firmly to do so). So, GCC attunes itself to its environment. ...to be continued... – Jonathan Leffler Apr 04 '10 at 05:51
  • @Tim: (cont'd) This observation does not detract from my observation that the standard talks in terms of a unified implementation of the C compiler plus the library. What the standards say and what implementations do in real life are different. The get-out clauses are numerous: (1) GCC doesn't claim to be strictly conformant to the standard in that it normally compiles with lots of extensions enabled; (2) even when it is trying to be strictly conformant, it probably relies on the library to do its part of the job (so if the library is non-standard, so is GCC). There are probably others too... – Jonathan Leffler Apr 04 '10 at 05:54
  • @Jonathan: You are correct, the C89/C90 standard requires a compiler and "The Standard C Library" with the functionality described in 13 header files. – tomlogic Apr 04 '10 at 15:50
-2

Yes you can do a ton of stuff without libraries.

The lifesaver is __asm__ in GCC. It is a keyword so yes you can.

Mostly because every programming language is built on Assembly, and you can make system calls directly under some OSes.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
-2

Assembly language has simple commands that move values to registers of the CPU, memory, and other basic functions, as well as perform the core capabilities and calculations of the machine. C libraries are basically chunks of assembly code. You can also use assembly code in your C programs. var is an assembly code instruction. When you use 0x before a number to make it Hex, that is assembly instruction. Assembly code is the readable form of machine code, which is the visual form of the actual switch states of the circuits paths.

So while the machine code, and therefore the assembly code, is built into the machine, C languages are combined of all kinds of pre-formed combinations of code, including your own functions that might be in part assembly language and in part calling on other functions of assembly language or other C libraries. So the assembly code is the foundation of all the programming, and after that it's anyone's guess about what is what. That's why there are so many languages and so few true standards.

Qantas 94 Heavy
  • 15,750
  • 31
  • 68
  • 83