38

Previously, I've only used Object Oriented Programming languages (C++, Ruby, Python, PHP), and am now learning C. I'm finding it difficult to figure out the proper way to do things in a language with no concept of an 'Object'. I realize that it's possible to use OOP paradigms in C, but I'd like to learn the C-idiomatic way.

When solving a programming problem, the first thing I do is to imagine an object that will solve the problem. What steps do I replace this with, when using a non-OOP Imperative Programming paradigm?

closed as primarily opinion-based by amon, user22815, Ixrec, Daenyth, DeadMG Jun 1 '15 at 20:34

Many good questions generate some degree of opinion based on expert experience, but answers to this question will tend to be almost entirely based on opinions, rather than facts, references, or specific expertise. If this question can be reworded to fit the rules in the help center, please edit the question.

  • 15
    I have yet to find a language that closely matches my way of thinking, so I have to “compile” my thoughts for any language I'm using. One concept I've found useful is that of a “code unit”, whether this is a label, subroutine, function, object, module, or framework: every one of them should be encapsulated and expose a well-defined interface. If you're used of a top-down object-level approach, in C you might start by drawing up a set of functions that behave as if the problem had been solved. Often, well-designed C APIs look like OOP, but qux = foo.bar(baz) becomes qux = Foo_bar(foo, baz). – amon May 31 '15 at 20:03
  • To echo amon, focus on the following: graph-like data structure, pointers, algorithms, the execution (control flow) of code (functions), function pointers. – rwong May 31 '15 at 20:05
  • 1
    LibTiff (source code on github) is an example of how to organize large C programs. – rwong May 31 '15 at 20:23
  • 1
    As a C# programmer I'd miss delegates (function pointers with one bound parameter) far more than I'd miss objects. – CodesInChaos Jun 1 '15 at 6:38
  • I personally found most of C easy and straightforward, with the notable exception of the pre-processor. If I had to re-learn C, that would be one area I'd concentrate a lot of my effort on. – biziclop Jun 1 '15 at 11:58
53
  • A C program is a collection of functions.
  • A function is a collection of statements.
  • You can encapsulate data with a struct.

That's it.

How did you write a class? That's pretty much how you write a .C file. Granted, you don't get things like method polymorphism and inheritance, but you can simulate those with different function names and composition anyway.

To pave the way, study Functional Programming. It's really quite amazing what you can do without classes, and some things actually work better without the overhead of classes.

Further Reading
Object-Orientation in ANSI C

  • 9
    you can also typedef that struct and make something class-like. and typedef-ed types can be included in other structs that themselves can be typedef-ed. what you don't get with C is operator overloading and the superficially simple inheritance of classes and the members within that you get in C++. and you don't get a lot of weird and unnatural syntax that you get with C++. i really love the concept of OOP, but i think C++ is an ugly realization of OOP. i like C because it is a smaller language and leaves out syntax from the language that is best left to functions. – robert bristow-johnson May 31 '15 at 22:52
  • 22
    As someone whose first language was/is C, I'd venture to say that a lot of things actually work better without the overhead of classes. – haneefmubarak Jun 1 '15 at 5:27
  • 1
    To expand, a lot of things have been developed without OOP: operating systems, protocol servers, boot loaders, browsers and so on. Computers do not think in terms of objects and neither do they need to. Indeed, it's often pretty slow for them to force that. – edmz Jun 1 '15 at 18:49
  • Counterpoint: a lot of things actually work better with addition of class-based OOP. Source: TypeScript, Dart, CoffeeScript and all the other ways industry is trying to get away from a functional / prototype OOP language. – Den Jun 29 '15 at 13:52
  • To expand, a lot of things have been developed with OOP: everything else. Humans do naturally think in terms of objects and programs are written for other humans to read and understand. – Den Jun 29 '15 at 13:55
18

Read SICP and learn Scheme, and the practical idea of abstract data types. Then coding in C is easy (since with SICP, a bit of C, and a bit of PHP, Ruby, etc... your thinking would be widen enough, and you would understand that object oriented programming might not be the best style in all cases, but only for some kind of programs). Be careful about C dynamic memory allocation, which is probably the hardest part. The C99 or C11 programming language standard and its C standard library is actually quite poor (it does not know about TCP or directories!), and you'll often need some external libraries or interfaces (e.g. POSIX, libcurl for HTTP client library, libonion for HTTP server library, GMPlib for bignums, some library like libunistring for UTF-8, etc...).

Your "objects" are often in C some related struct-s, and you define the set of functions operating on them. For short or very simple functions, consider defining them, with the relevant struct, as static inline in some header file foo.h to be #include-d elsewhere.

Notice that object oriented programming is not the only programming paradigm. In some occasions, other paradigms are worthwhile (functional programming à la Ocaml or Haskell or even Scheme or Commmon Lisp, logic programming à la Prolog, etc etc... Read also J.Pitrat's blog about declarative artificial intelligence). See Scott's book: Programming Language Pragmatics

Actually, a programmer in C, or in Ocaml, usually does not want to code in an object oriented programming style. There is no reason to force yourself to think of objects when that is not useful.

You'll define some struct and the functions operating on them (often thru pointers). You could need some tagged unions (often, a struct with a tag member, often some enum, and some union inside), and you might find useful to have a flexible array member at the end of some of your struct-s.

Look inside the source code of some existing free software in C (see github & sourceforge to find some). Probably, installing and using a Linux distribution would be useful: it is made almost only of free software, it has great free software C compilers (GCC, Clang/LLVM) and development tools. See also Advanced Linux Programming if you want to develop for Linux.

Don't forget to compile with all warnings and debug info, e.g. gcc -Wall -Wextra -g -notably during the development & debugging phases- and learn to use some tools, e.g. valgrind to hunt memory leaks, the gdb debugger, etc. Take care to understand well what is undefined behavior and strongly avoid it (remember that a program could have some UB and sometimes seem to "work").

When you genuinely need object oriented constructs (in particular inheritance) you can use pointers to related structures and to functions. You could have your own vtable machinery, have each "object" starting with a pointer to a struct containing function pointers. You take advantage of the ability to cast a pointer type to another pointer type (and of the fact that you can cast from a struct super_st containing the same field types as those starting a struct sub_st to emulate inheritance). Notice that C is enough to implement quite sophisticated object systems -in particular by following some conventions-, as GObject (from GTK/Gnome) demonstrates.

When you genuinely need closures, you'll often emulate them with callbacks, with the convention that every function using a callback is passed both a function pointer and some client data (consumed by the function pointer when it calls that). You could also have (conventionally) your own closure-like struct-s (containing some function pointer and the closed values).

Since C is a very low level language, it is important to define and document your own conventions (inspired by the practice in other C programs), in particular about memory management, and probably some naming conventions also. It is useful to have some idea about instruction set architecture. Don't forget that a C compiler may do lots of optimizations on your code (if you ask it to), so don't care too much about doing micro-optimizations by hand, leave that to your compiler (gcc -Wall -O2 for optimized compilation of released software). If you care about benchmarking and raw performance, you should enable optimizations (once your program has been debugged).

Don't forget that sometimes metaprogramming is useful. Quite often, large software written in C contain some scripts or ad-hoc programs to generate some C code used elsewhere (and you might also play some dirty C preprocessor tricks, e.g. X-macros). There exists some useful C program generators (e.g. yacc or gnu bison to generate parsers, gperf to generate perfect hash functions, etc...). On some systems (notably Linux & POSIX) you could even generate some C code at runtime in generated-001.c file, compile it to a shared object by running some command (like gcc -O -Wall -shared -fPIC generated-001.c -o generated-001.so) at runtime, dynamically load that shared object using dlopen & get a function pointer from a name using dlsym. I'm doing such tricks in MELT (a Lisp-like domain specific language which might be useful to you, since it enables customization of the GCC compiler).

Be aware of garbage collection concepts and techniques (reference counting is often a technique to manage memory in C, and it is IMHO a poor form of garbage collection which does not deal well with circular references; you could have weak pointers to help about that, but it might be tricky). On some occasions, you might consider using Boehm's conservative garbage collector.

  • 7
    Honestly, indepently from this question, reading SICP is undoubtly a good advice, but for the OP this will probably lead to the next question "How to think as a C programmer after biased with SICP". – Doc Brown Jun 1 '15 at 11:40
  • 1
    No, because the Scheme from SICP & PHP (or Ruby or Python) are so different that the OP would get a much more wider thinking; and SICP explains quite well what is abstract data type in practice, and that is very useful to understand, in particular for coding in C. – Basile Starynkevitch Jun 1 '15 at 11:46
  • 1
    SICP is a strange suggestion. Scheme is very different from C. – Brian Gordon Jun 1 '15 at 20:13
  • But SICP is teaching a lot of good habits, and knowing Scheme does help when coding in C (for the concepts of closures, abstract data types, etc...) – Basile Starynkevitch Jun 1 '15 at 20:25
5

The way the program is built is basically defining which actions (functions) must be performed in order to solve the problem (that is why it is called procedural language). Each action will correspond to a function. Then you need to define what kind of information each function will receive and what information they need to return.

The program is normally separated in files (modules) each file will normally have a group of functions that are related. In the beginning of each file you declare (outside any function) variables that will be used by all functions in that file. If you use the "static" qualifier those variables will be only visible inside that file (but not from other files). If you don't use "static" qualifier on variables defined outside of functions they will be accessible from other files as well and these other files should declare the variable as "extern" (but not define it) so the compiler will look for them in other files.

So in short you first think about the procedures (functions) then you make sure all function have access to the information they need.

3

C APIs often -- perhaps even, usually -- do have an essentially object oriented interface if you look at them the right way.

In C++:

class foo {
    public:
        foo (int x);
        void bar (int param);
    private:
        int x;
};

// Example use:
foo f(42);
f.bar(23);

In C:

typedef struct {
    int x;
} foo;

void bar (foo*, int param);

// Example use:
foo f = { .x = 42 };
bar(&f, 23);

As you may be aware, in C++ and various other formal OO languages, under the hood an object method takes a first argument that is a pointer to the object, much like the C version of bar() above. For an example of where this comes to the surface in C++, consider how std::bind can be used to fit object methods to function signatures:

new function<void(int)> (
    bind(&foo::bar, this, placeholders::_1)
//                  ^^^^ object pointer as first arg
);

As others have pointed out, the real difference is that formal OO languages may implement polymorphism, access control, and various other nifty features. But the essence of object oriented programming, the creation and manipulation of discrete, complex data structures, is already a fundamental practice in C.

2

One of the big reasons people are encouraged to learn C is that's it's one of the lowest of the high-level programming languages. OOP languages make it easier to think about data models and templating code and message passing, but at the end of the day, a microprocessor executes code step-by-step, jumping in and out of blocks of code (functions in C) and moving references to variables (pointers in C) around so that different parts of a program can share data. Think of C as assembly language in English -- providing step by step instructions to your computer's microprocessor -- and you won't go far wrong. As a bonus, most operating system interfaces work like C function calls rather than OOP paradigms, so learning C will also tell you a lot about how code execution moves from one program (the OS) to another (your program) and back again.

  • 2
    IMHO C is a low level language, but much higher than assembler or machine code, since the C compiler could do a lot of low-level optimizations. – Basile Starynkevitch Jun 1 '15 at 13:35
  • C compilers are also, in the name of "optimization", moving toward an abstract machine model which may negate the laws of time and causality when given input which would cause Undefined Behavior, even if the natural behavior of the code on the machine where it is run would meet otherwise meet requirements. For example, the function uint16_t blah(uint16_t x) {return x*x;} will work identically on machines where unsigned int is 16 bits, or where it is 33 bits or larger. Some compilers for machines where unsigned int is 17 to 32 bits, however, may regard a call to that method... – supercat Jun 1 '15 at 20:02
  • ...as granting permission for the compiler to infer that no chain of events which would cause the method to be given a value in excess of 46340 could possibly occur. Even though multiplying 65533u * 65533u on any platform would yield a value which, when cast to uint16_t, would yield 9, the Standard does not mandate such behaviors when multiplying values of type uint16_t on 17- to 32-bit platforms. – supercat Jun 1 '15 at 20:08
-1

I am also I OO native ( C++ generally ) who sometimes has to survive in a world of C. To me the fundamentally biggest hurdle is dealing with error handling and resource management.

In C++ we have throw to pass an error from where it happens all the way back to the top level where we can deal with it and we have destructors to automatically free our memory and other resources.

You might notice that many C APIs include an init function which provides you with a typedef'd void * which is really a pointer to a struct. Then you pass this in as the first argument for every API call. Essentially that becomes your "this" pointer from C++. It gets used for all the internal data structures which are hidden away (a very OO concept). You can also use it to manage memory, e.g. have a function called myapiMalloc which mallocs your memory and records the malloc in your C version of a this pointer so you can make sure it is freed when your API returns. Also as I recently discovered you can use it to store error codes and use setjmp and longjmp to give you behaviour very similar to throw catch. Combining both concepts gives you a lot of the functionality of a C++ program.

Now you did say that you didn't want to learn to force C into C++. That isn't really what I'm describing (at least not deliberately). This is simply a (hopefully) well designed method to exploit C functionality. It does turn out to have some OO flavours - perhaps that's why OO languages developed, they were a way to formalise/enforce/facilitate concepts that some people found to be best practice.

If you think this is to OO feeling for you then the alternative is to have pretty much every function return an error code which you must religiously ensure you check after every function call and propagate up the call stack. You must ensure that all resources are freed not only at the end of each function, but at every return point (which potentially could be after any function call which can return an error that indicates you cannot continue). It can get very tedious and tends to lead to you thinking I probably don't need to deal with that potential memory allocation failure (or file read or port connect...), I'll just assume that it will work or I'll write the "interesting" code now and come back and deal with the error handling - which never happens.

Not the answer you're looking for? Browse other questions tagged or ask your own question.