High-level and underlying C

Source: Internet
Author: User

 

High and low-level C

High-level and underlying C

 

Jim Larson

1996-09-13

 

This talk was given at the Section 312 programming lunchtime seminar.

 

Introduction

Introduction

Tower of computing ages. High-level computing ages can (mostly) Compile to lower-level ones.

In the language Tower, almost all high-level languages can be compiled into the underlying language.

Might want to write in low-level language for access to hardware level. Can write high-level code in low-level language by compiling in your head.

You may want to write the underlying language for accessing the hardware layer. By compiling in your mind, you can write high-level languages in the underlying language.

Might want to write in High-level language for flexibility and power. Can write low-level code in High-level language by "writing through the compiler ".

You may want to write high-level languages with flexibility and strength. By using compilers, you can write the underlying language in high-level languages.

 

C features

C features

 

Recursive functions

Recursive functions

 

C has a stack used for the call stack, activation records, and local variables.

Note that functions are not nested, as in Pascal. This affords greater freedoms for using function pointers.

C has a stack for calling, activity recording, and local variables. Note that functions cannot be nested in Pascal. Function pointers provide greater freedom.

 

/*

* Simple example of a recursive function.

*/

Unsigned

FIB (unsigned N)

{

If (n = 0 | n = 1)

Return 1;

Else

Return fib (n-1) + fib (n-2 );

}

This simple example is a little contrived, as well as a lousy way to compute Fibonacci numbers. A better example shows the natural relation between recursive functions and recursive data structures-those that have references to other objects of the same type.

As a simple example of poor computation of the Fibonacci series, the Code means it. A better example shows the natural relationship between recursive functions and recursive data structures (objects that reference other data structures of the same type ).

/*

* Recursive functions work well with recursive data structures.

* Recursive functions work well with recursive Data Structures

*/

Typedef struct expr;

Struct expr {

Enum {number, plus, minus, times, divide} op;

Union {

Double number;

Struct {

Expr * left, * right;

} Child;

} U;

};

 

Double

Eval (expr * E)

{

Switch (e-> OP ){

Case number: Return e-> U. number;

Case plus: Return eval (e-> U. Child. Left) + eval (e-> U. Child. Right );

Case minus: Return eval (e-> U. Child. Left)-eval (e-> U. Child. Right );

Case times: Return eval (e-> U. Child. Left) * eval (e-> U. Child. Right );

Case divide: Return eval (e-> U. Child. Left)/eval (e-> U. Child. Right );

}

}

Dynamic Memory Allocation

Dynamic Memory Allocation

 

Stack-allocation-local variables.

Static Memory Allocation-local variables

Heap-allocation. Library only, but pervasively used. (actually, this is our first example of a high-level feature implemented entirely in Standard C .)

Heap allocation, used only for databases, but is widely used (in fact, this is our first high-level implementation example, fully implemented using standard C)

ABSTRACT Data Types

ABSTRACT Data Type

 

C type theory is kind of confusing: Types where you know the size of the object types where you don't know the size of the object void *

Tricks: You can declare and use a pointer to an incomplete type. you can complete an incomplete type later. pointers to structures can be coerced to and from pointers to their first element, if it also is a structure.

The C-type theory is a obfuscation: You know the size of the object type, but you do not know the size of the void * object type.

Tip: You can declare a pointer of an incomplete type and then complete the type. The pointer pointing to the structure and the pointer pointing to the first element (if it is also a structure) can be forcibly converted.

/* In widget. H */

Typedef struct widget;

 

Extern widget * widget_new (double length, Enum color );

Extern double widget_mass (widget * w );

Extern int widget_fitsin (widget * w_inner, widget * w_outer );

Extern void widget_delete (widget * w );

The implementation gets to hide information about the representation, as well as guarantee invariants.

/* In widget. C */

# Include <stdlib. h>

# Include "Colors. H"

# Include "widget. H"

 

/*

* Non-Public definition of widget structure, declared in "widget. H ".

*/

Struct widget {

Widget * Next;/* widgets are stored on a linked list */

Int ID;/* identification stamp */

Double Length;/* length in centimeters */

Double Mass;/* Mass in grams */

Enum color;/* See "Colors. H" for definitions */

};

 

Static const double widget_height = 2.54;/* in centimeters */

Static const double widget_density = 1.435;/* in g/cm ^ 3 */

Static widget * widget_list = 0;

Static int widget_next_id = 0;

 

/*

* Create a new widget. Calculate widget mass. Keep track

* Bookkeeping with ID number and store it on linked list of widgets.

*/

Widget *

Widget_new (double length, Enum color)

{

Widget * w = malloc (sizeof (widget ));

 

If (! W)

Return 0;

W-> next = widget_list;

Widget_list = W;

W-> id = widget_next_id ++;

W-> length = length;

W-> mass = 0.5 * length * widget_height * widget_density;

W-> color = color;

Return W;

}

Nonlocal exits

Non-local presence

 

Setjmp/longjmp work like a bunch of immediate returns from functions. Intermediate functions don't need to make provisions for this-modular way to raise error conditions.

Viewing function call/Return Sequences (aka procedure activations) as a tree, longjump can only work on a saved jmp_buf from a parent in the tree.

Setjmp/longjmp works like returning a series of functions. Intermediate functions do not need to handle modular methods to throw error conditions.

 

# Include <signal. h>

# Include <setjmp. h>

 

Static jmp_buf begin;

 

Static void

Fpecatch (INT sig)

{

Warning ("floating point exception ");

Longjmp (begin, 0 );

}

 

Void

Command_loop (void)

{

For (;;){

If (setjmp (BEGIN )){

Printf ("command failed to execute! /N ");

}

Signal (sigfpe, & fpecatch );

Prompt ();

Do_command (read_command ());

}

}

High-Level C

High-level C

 

Classes and objects

Class and Object

 

The core of OO is "dynamic dispatch"-you don't know which function you're calling. function pointers also provide this kind of indirection.

Structure coercion and nesting provide for single-inheritance of classes.

The core of OO is "Dynamic Allocation"-you don't know which function you are calling. Function pointers also provide this indirect property.

 

Method callcan be masked by functions or macros. Can "crack open" the specified action to cache methods.

Method calls can be concealed by functions or macros. It can be abstracted from "hacker-type open" to the cache method.

 

/* In shape. H */

Typedef struct point;

Struct point {

Double X, Y;

};

 

Typedef struct shape;

Struct shape {

Void (* move) (shape * Self, point P );

Void (* scale) (shape * Self, double factor );

Void (* rotate) (shape * Self, double degrees );

Void (* redraw) (shape * Self );

};

 

Extern shape * make_triangle (point center, double size );

In the implementation:

In implementation:

/* In triangle. C */

# Include <stdlib. h>

# Include "shape. H"

 

Typedef struct triangle;

Struct triangle {

Shape OPS;

Point center;

Point voffset [3];

};

 

Static void

Tri_move (shape * Self, point P)

{

Triangle * t = (Triangle *) self;

 

T-> center = P;

}

 

Static void

Tri_scale (shape * Self, double factor)

{

Triangle * t = (Triangle *) self;

Int I;

 

For (I = 0; I <3; ++ I ){

T-> voffset [I]. x * = factor;

T-> voffset [I]. y * = factor;

}

}

 

Static void

Tri_redraw (shape * Self)

{

Triangle * t = (Triangle *) self;

Point c = T-> center;

Point V0 = addpoint (C, T-> voffset [0]);

Point V1 = addpoint (C, T-> voffset [1]);

Point V2 = addpoint (C, T-> voffset [2]);

 

Drawline (v0, V1 );

Drawline (V1, V2 );

Drawline (V2, V0 );

}

 

Shape triangle_ops = {& tri_move, & tri_redraw, & tri_scale, & tri_rotate };

 

Shape *

Make_triangle (point center, double size)

{

Triangle * t = malloc (sizeof (triangle ));

 

If (! T)

Return 0;

T-> Ops = triangle_ops;

T-> center = center;

T-> voffset [0]. x = size * v0x;

T-> voffset [0]. Y = size * v0y;

T-> voffset [1]. x = size * v1x;

T-> voffset [1]. Y = size * v1y;

T-> voffset [2]. x = size * v2x;

T-> voffset [2]. Y = size * v2y;

Return & T-> OPS;

}

In a client module that uses the interface:

The client module that uses this interface:

/* In animate. C */

Void

Pulsate (shape * s, double period)

{

Double factor = 1.0, step = 0.1;

Int I;

Void (* scale) (shape *, double) = S-> scale;/* cache Method */

Void (* redraw) (shape *) = S-> redraw;

 

For (;;){

For (I = 0; I <10; ++ I ){

Factor + = step;

(* Scale) (S, factor );

(* Redraw) (s );

Sleep (1 );

}

Step * =-1.0;

}

}

Closures

Closure

 

In scheme, abstractions carry their environment with them. this is like bundling a function pointer and some data to work. the data acts like "configuration" data. can either make it an explicit argument, or create ADT for closure and make "apply_closure" function-breaks from ordinary function syntax.

Much like objects, implemented above, but less constrained in use.

In scheme, abstraction carries their own environment, just like function pointers and related data. The data can be an explicit parameter like the configuration data, or an ADT is created from the closure, and the apply_closure function is used, although this breaks the regular function syntax.

/* In closure. H */

Typedef struct closure;

Struct closure {

Void * (* fN) (void *);

Void * data;

}

 

Inline void *

Appclosure (Closure * t)

{

Return (* t-> FN) (t-> data );

}

Exception Handling

Exception Handling

 

Want to be able to raise a certain kind of error to by handled by a designated handler, but with the "linkage" established dynamically.

If you want to be able to raise a specific type of error through a set exception, you can dynamically link it (it is difficult to translate this sentence)

# Include "exception. H"

 

Void

Logcommands (char * filename)

{

If (! (F = fopen (filename )))

Throw_error (err_filename );

 

Catch_error (all_errs ){

Fflush (f );

Fclose (f );

Throw_error (throwerror );

} Errors_in {

While (x = read_input (stdin ))! = EOF ){

A = compute_result (X );

Print_result (A, F );

}

} End_error;

}

The implementation is kind of tricky-Use of ternary expression to make complicated series of tests into a syntactic expression:

This implementation is a trick-using a ternary expression to create a test of a complex sequence into a syntax expression

/* In exception. H */

 

Const int maxcatchers' = 100;

Extern jmp_buf catchers [maxcatchers];

Extern volatile int nextcatch;

Extern volatile int throwerror;

 

# Define all_errs 0

# Define catch_error (e )/

If (nextcatch = maxcatchers )/

? Error ("too handle exception handlers ")/

: (Setjmp (catchers [nextcatch ++]) = 0 )/

? 0/

: (E )! = All_errs & throwerror! = (E ))/

? Longjmp (catchers [-- nextcatch])/

: 1)

# Define errors_in else

# Define end_error do {-- nextcatch;} while (0)

 

# Define throw_error (e )/

Do {/

Throwerr = (E );/

Longjmp (catchers [-- nextcatch]);/

} While (0)

 

Continuations

Coroutine

 

Scheme's general continuations will let you resume at any previous location in call tree-cool for escapes, coroutines, backtracking, etc.

Setjmp/longjmp will let you jump up to a known location. Disciplined use can result in catch/throw.

Scheme's general coroutine allows you to restore the call tree to the previous position-for escape, coroutine and direction tracking are cool.

 

By making stacks explicit, you can do coroutines.

By making the stack explicit, You can implement coroutine

 

By using a continuation-Passing Style, you can do arbitrarily cool things using Cheney on the MTA. See paper by Henry Baker.

By using the coroutine transfer style, you can use Cheney on the MTA to do anything cool. See Henry Baker's paper.

Typedef void * (* genfun) (void );

 

Void

Trampoline (genfun F)

{

While (f)

F = (genfun) (* f )();

}

 

Garbage-collected memory

Garbage collection memory

 

Through explicit maintenance of roots into the GC 'd heap, and the types of objects, you can safely ignore free () and the associate bookkeeping.

"Conservative GC" implementations allow GC behavior for standard programs. See work by Hans Boehm, or the specified cial product by geodesic systems.

By explicitly maintaining the root and object types in the GC heap, you can safely ignore free and related bookkeeping work.

Cheney on the MTA gives simple generational capabilities.

The Cheney on MTA provides simple model capabilities.

 

Low-level C

Underlying C

 

Bounded memory usage

Bounded memory usage

 

Ted drain's idea: Have a malloc () substitute profile allocation for a sample run, then can build a dedicated Allocator for a heap whose size is known at compile-time.

The idea of Ted drain: for simple operations, create a malloc replacement profiling allocation, and then create a dedicated distributor for the heap. The heap size is known during compilation.

# Include <assert. h>

# Include <stdio. h>

# Include <stdlib. h>

 

Static size_t allocbytes = 0;

 

Void

Printalloc (void)

{

Fprintf (stderr, "allocated % lu Bytes/N", (unsigned long) allocbytes );

}

 

Void

Ememinit (void)

{

Int err;

 

Err = atexit (& printalloc );

Assert (ERR = 0 );

}

 

Void *

Emalloc (size_t size)

{

Void * P = malloc (size );

 

Assert (P );

Allocbytes + = size;

Return P;

}

 

Bounded call stack depth

Bounded call stack depth

 

Trampoline back and manage stack explicitly.

Can also implement a State-machine within a function.

An explicit management stack is returned from a conventional method, and a state machine can be implemented in a function.

 

Data Type Representation

Data Type Representation

 

Structure Ordering and offsets-make explicit with arrays.

Byte-level representation-use arrays of characters. Portable binary representations of output.

Structural order and offset-use an array for explicit implementation.

Direct hierarchical representation-use the array feature to output portable binary representation

 

Typedef char limit 4hdr [20];

 

Struct {

Char * fieldname;

Int byteoffset;

Int bitoffset;

Int bitlength;

} Ipv4fields [] = {

{"Vers", 0, 0, 4 },

{"Hlen", 0, 4, 4 },

{"Service type", 1, 0, 8 },

{"Total length", 2, 0, 16 },

{"Identification", 4, 0, 16 },

{"Flags", 6, 0, 3 },

{"Fragment offset", 6, 3, 13 },

{"Time to live", 8, 0, 8 },

{"Protocol", 9, 0, 8 },

{"Header checksum", 10, 0, 16 },

{"Source IP address", 12, 0, 32 },

{"Desination IP Address", 16, 0, 32}

};

 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.