Since C does not have object-oriented features, there's a separate section for object-oriented issues Programming in D for C++ Programmers.
The C preprocessor is covered in The C Preprocessor vs D.
sizeof(int) sizeof(char *) sizeof(double) sizeof(struct Foo)
Use the size property:
int.size (char *).size double.size Foo.size
#include <limits.h> #include <math.h> CHAR_MAX CHAR_MIN ULONG_MAX DBL_MIN
char.max char.min ulong.max double.min
bool => bit char => char signed char => byte unsigned char => ubyte short => short unsigned short => ushort wchar_t => wchar int => int unsigned => uint long => int unsigned long => uint long long => long unsigned long long => ulong float => float double => double long double => extended _Imaginary long double => imaginary _Complex long double => complex
Although char is an unsigned 8 bit type, and wchar is an unsigned 16 bit type, they have their own separate types in order to aid overloading and type safety.
Ints and unsigneds in C are of varying size; not so in D.
#include <fp.h> NAN INFINITY #include <float.h> DBL_DIG DBL_EPSILON DBL_MANT_DIG DBL_MAX_10_EXP DBL_MAX_EXP DBL_MIN_10_EXP DBL_MIN_EXP
double.nan double.infinity double.dig double.epsilon double.mant_dig double.max_10_exp double.max_exp double.min_10_exp double.min_exp
#include <math.h> float f = fmodf(x,y); double d = fmod(x,y); long double e = fmodl(x,y);
D supports the remainder ('%') operator on floating point operands:
float f = x % y; double d = x % y; extended e = x % y;
C doesn't define what happens if an operand to a compare is NAN, and few C compilers check for it (the Digital Mars C compiler is an exception, DM's compilers do check for NAN operands).
#include <math.h> if (isnan(x) || isnan(y)) result = FALSE; else result = (x < y);
D offers a full complement of comparisons and operators that work with NAN arguments.
result = (x < y); // false if x or y is nan
C doesn't directly support assert, but does support __FILE__ and __LINE__ from which an assert macro can be built. In fact, there appears to be practically no other use for __FILE__ and __LINE__.
#include <assert.h> assert(e == 0);
D simply builds assert into the language:
assert(e == 0);
[NOTE: trace functions?]
#define ARRAY_LENGTH 17 int array[ARRAY_LENGTH]; for (i = 0; i < ARRAY_LENGTH; i++) array[i] = value;
int array[17]; array[] = value;
The array length is defined separately, or a clumsy sizeof() expression is used to get the length.
#define ARRAY_LENGTH 17 int array[ARRAY_LENGTH]; for (i = 0; i < ARRAY_LENGTH; i++) func(array[i]);or:
int array[17]; for (i = 0; i < sizeof(array) / sizeof(array[0]); i++) func(array[i]);
The length of an array is accessible the property "length".
int array[17]; for (i = 0; i < array.length; i++) func(array[i]);or even better:
int array[17]; foreach (int value; array) func(value);
#include <stdlib.h> int array_length; int *array; int *newarray; newarray = (int *) realloc(array, (array_length + 1) * sizeof(int)); if (!newarray) error("out of memory"); array = newarray; array[array_length++] = x;
int[] array; array.length = array.length + 1; array[array.length - 1] = x;
There are several difficulties to be resolved, like when can storage be free'd, dealing with null pointers, finding the length of the strings, and memory allocation:
#include <string.h> char *s1; char *s2; char *s; // Concatenate s1 and s2, and put result in s free(s); s = (char *)malloc((s1 ? strlen(s1) : 0) + (s2 ? strlen(s2) : 0) + 1); if (!s) error("out of memory"); if (s1) strcpy(s, s1); else *s = 0; if (s2) strcpy(s + strlen(s), s2); // Append "hello" to s char hello[] = "hello"; char *news; size_t lens = s ? strlen(s) : 0; news = (char *)realloc(s, (lens + sizeof(hello) + 1) * sizeof(char)); if (!news) error("out of memory"); s = news; memcpy(s + lens, hello, sizeof(hello));
char[] s1; char[] s2; char[] s; s = s1 ~ s2; s ~= "hello";
#include <stdio.h> printf("Calling all cars %d times!\n", ntimes);
import stdio; printf("Calling all cars %d times!\n", ntimes);
void forwardfunc(); void myfunc() { forwardfunc(); } void forwardfunc() { ... }
void myfunc() { forwardfunc(); } void forwardfunc() { ... }
void function(void);
void function() { ... }
for (i = 0; i < 10; i++) { for (j = 0; j < 10; j++) { if (j == 3) goto Louter; if (j == 4) goto L2; } L2: ; } Louter: ;
Louter: for (i = 0; i < 10; i++) { for (j = 0; j < 10; j++) { if (j == 3) break Louter; if (j == 4) continue Louter; } } // break Louter goes here
typedef struct ABC { ... } ABC;
struct ABC { ... };
#include <string.h> void dostring(char *s) { enum Strings { Hello, Goodbye, Maybe, Max }; static char *table[] = { "hello", "goodbye", "maybe" }; int i; for (i = 0; i < Max; i++) { if (strcmp(s, table[i]) == 0) break; } switch (i) { case Hello: ... case Goodbye: ... case Maybe: ... default: ... } }The problem with this is trying to maintain 3 parallel data structures, the enum, the table, and the switch cases. If there are a lot of values, the connection between the 3 may not be so obvious when doing maintenance, and so the situation is ripe for bugs. Additionally, if the number of values becomes large, a binary or hash lookup will yield a considerable performance increase over a simple linear search. But coding these can be time consuming, and they need to be debugged. It's typical that such just never gets done.
void dostring(char[] s) { switch (s) { case "hello": ... case "goodbye": ... case "maybe": ... default: ... } }Adding new cases becomes easy. The compiler can be relied on to generate a fast lookup scheme for it, eliminating the bugs and time required in hand-coding one.
#pragma pack(1) struct ABC { ... }; #pragma pack()But #pragmas are nonportable both in theory and in practice from compiler to compiler.
struct ABC { int z; // z is aligned to the default align (1) int x; // x is byte aligned align (4) { ... // declarations in {} are dword aligned } align (2): // switch to word alignment from here on int y; // y is word aligned }
struct Foo { int i; union Bar { struct Abc { int x; long y; } _abc; char *p; } _bar; }; #define x _bar._abc.x #define y _bar._abc.y #define p _bar.p struct Foo f; f.i; f.x; f.y; f.p;Not only is it clumsy, but using macros means a symbolic debugger won't understand what is being done, and the macros have global scope instead of struct scope.
struct Foo { int i; union { struct { int x; long y; } char* p; } } Foo f; f.i; f.x; f.y; f.p;
struct Foo { int x; int y; } foo;Or to separate the two:
struct Foo { int x; int y; }; // note terminating ; struct Foo foo;
struct Foo { int x; int y; } // note there is no terminating ; Foo foo;which means that the terminating ; can be dispensed with, eliminating the confusing difference between struct {} and function & block {} in how semicolons are used.
#include <stddef> struct Foo { int x; int y; }; off = offsetof(Foo, y);
struct Foo { int x; int y; } off = Foo.y.offset;
union U { int a; long b; }; union U x = { 5 }; // initialize member 'a' to 5Adding union members or rearranging them can have disastrous consequences for any initializers.
union U { int a; long b; } U x = { a:5 }avoiding the confusion and maintenance problems.
struct S { int a; int b; }; struct S x = { 5, 3 };This isn't much of a problem with small structs, but when there are numerous members, it becomes tedious to get the initializers carefully lined up with the field declarations. Then, if members are added or rearranged, all the initializations have to be found and modified appropriately. This is a minefield for bugs.
struct S { int a; int b; } S x = { b:3, a:5 }The meaning is clear, and there no longer is a positional dependence.
int a[3] = { 3,2,2 };Nested arrays may or may not have the { }:
int b[3][2] = { 2,3, {6,5}, 3,4 };
int[3] a = [ 3, 2, 0 ]; int[3] a = [ 3, 2 ]; // unsupplied initializers are 0, just like in C int[3] a = [ 2:0, 0:3, 1:2 ]; int[3] a = [ 2:0, 0:3, 2 ]; // if not supplied, the index is the previous // one plus one.This can be handy if the array will be indexed by an enum, and the order of enums may be changed or added to:
enum color { black, red, green } int[3] c = [ black:3, green:2, red:5 ];Nested array initializations must be explicit:
int[2][3] b = [ [2,3], [6,5], [3,4] ]; int[2][3] b = [[2,6,3],[3,5,4]]; // error
char file[] = "c:\\root\\file.c";This gets even more unpleasant with regular expressions. Consider the escape sequence to match a quoted string:
/"[^\\]*(\\.[^\\]*)*"/
In C, this horror is expressed as:
char quoteString[] = "\"[^\\\\]*(\\\\.[^\\\\]*)*\"";
char[] file = 'c:\root\file.c'; char[] quoteString = \" r"[^\\]*(\\.[^\\]*)*" \";The famous hello world string becomes:
char[] hello = "hello world" \n;
Modern programming requires that wchar strings be supported in an easy way, for internationalization of the programs.
#include <wchar.h> char foo_ascii[] = "hello"; wchar_t foo_wchar[] = L"hello";Things get worse if code is written to be both ascii and wchar compatible. A macro is used to switch strings from ascii to wchar:
#include <tchar.h> tchar string[] = TEXT("hello");
char[] foo_ascii = "hello"; // string is taken to be ascii wchar[] foo_wchar = "hello"; // string is taken to be wchar
enum COLORS { red, blue, green, max }; char *cstring[max] = {"red", "blue", "green" };This is fairly easy to get right because the number of entries is small. But suppose it gets to be fairly large. Then it can get difficult to maintain correctly when new entries are added.
enum COLORS { red, blue, green } char[][COLORS.max + 1] cstring = [ COLORS.red : "red", COLORS.blue : "blue", COLORS.green : "green", ];Not perfect, but better.
typedef void *Handle; void foo(void *); void bar(Handle); Handle h; foo(h); // coding bug not caught bar(h); // okThe C solution is to create a dummy struct whose sole purpose is to get type checking and overloading on the new type.
struct Handle__ { void *value; } typedef struct Handle__ *Handle; void foo(void *); void bar(Handle); Handle h; foo(h); // syntax error bar(h); // okHaving a default value for the type involves defining a macro, a naming convention, and then pedantically following that convention:
#define HANDLE_INIT ((Handle)-1) Handle h = HANDLE_INIT; h = func(); if (h != HANDLE_INIT) ...For the struct solution, things get even more complex:
struct Handle__ HANDLE_INIT; void init_handle() // call this function upon startup { HANDLE_INIT.value = (void *)-1; } Handle h = HANDLE_INIT; h = func(); if (memcmp(&h,&HANDLE_INIT,sizeof(Handle)) != 0) ...There are 4 names to remember: Handle, HANDLE_INIT, struct Handle__, value.
typedef void* Handle; void foo(void*); void bar(Handle); Handle h; foo(h); bar(h);To handle a default value, add an initializer to the typedef, and refer to it with the .init property:
typedef void* Handle = cast(void*)(-1); Handle h; h = func(); if (h != Handle.init) ...There's only one name to remember: Handle.
struct A x, y; ... x = y;it does not for struct comparisons. Hence, to compare two struct instances for equality:
#include <string.h> struct A x, y; ... if (memcmp(&x, &y, sizeof(struct A)) == 0) ...Note the obtuseness of this, coupled with the lack of any kind of help from the language with type checking.
There's a nasty bug lurking in the memcmp(). The layout of a struct, due to alignment, can have 'holes' in it. C does not guarantee those holes are assigned any values, and so two different struct instances can have the same value for each member, but compare different because the holes contain different garbage.
A x, y; ... if (x == y) ...
char string[] = "hello"; if (strcmp(string, "betty") == 0) // do strings match? ...C uses 0 terminated strings, so the C way has an inherent inefficiency in constantly scanning for the terminating 0.
char[] string = "hello"; if (string == "betty") ...D strings have the length stored separately from the string. Thus, the implementation of string compares can be much faster than in C (the difference being equivalent to the difference in speed between the C memcmp() and strcmp()).
D supports comparison operators on strings, too:
char[] string = "hello"; if (string < "betty") ...which is useful for sorting/searching.
int compare(const void *p1, const void *p2) { type *t1 = (type *)p1; type *t1 = (type *)p2; return *t1 - *t2; } type array[10]; ... qsort(array, sizeof(array)/sizeof(array[0]), sizeof(array[0]), compare);A compare() must be written for each type, and much careful typo-prone code needs to be written to make it work.
type[] array; ... array.sort; // sort array in-place
volatile int *p = address; i = *p;
int* p = address; volatile { i = *p; }
"This text spans\n\ multiple\n\ lines\n"If there is a lot of text, this can wind up being tedious.
"This text spans multiple lines "So blocks of text can just be cut and pasted into the D source.
To make this work, a helper function membersearchx is needed to recursively walk the trees. The helper function needs to read and write some context outside of the trees, so a custom struct Paramblock is created and a pointer to it is used to maximize efficiency.
struct Symbol { char *id; struct Symbol *left; struct Symbol *right; }; struct Paramblock { char *id; struct Symbol *sm; }; static void membersearchx(struct Paramblock *p, struct Symbol *s) { while (s) { if (strcmp(p->id,s->id) == 0) { if (p->sm) error("ambiguous member %s\n",p->id); p->sm = s; } if (s->left) membersearchx(p,s->left); s = s->right; } } struct Symbol *symbol_membersearch(Symbol *table[], int tablemax, char *id) { struct Paramblock pb; int i; pb.id = id; pb.sm = NULL; for (i = 0; i < tablemax; i++) { membersearchx(pb, table[i]); } return pb.sm; }
The performance of the two versions is indistinguishable.
class Symbol { char[] id; Symbol left; Symbol right; } Symbol symbol_membersearch(Symbol[] table, char[] id) { Symbol sm; void membersearchx(Symbol s) { while (s) { if (id == s.id) { if (sm) error("ambiguous member %s\n", id); sm = s; } if (s.left) membersearchx(s.left); s = s.right; } } for (int i = 0; i < table.length; i++) { membersearchx(table[i]); } return sm; }
int i, j; ... j = (unsigned)i >> 3;If i is an int, this works fine. But if i is of a typedef'd type,
myint i, j; ... j = (unsigned)i >> 3;and myint happens to be a long int, then the cast to unsigned will silently throw away the most significant bits, corrupting the answer.
myint i, j; ... j = i >>> 3;avoids the unsafe cast and will work as expected with any integral type.
A generic context pointer is also needed, represented here by void *p. The example here is of a trivial container class that holds an array of int's, and a user of that container that computes the maximum of those int's.
struct Collection { int array[10]; void apply(void *p, void (*fp)(void *, int)) { for (int i = 0; i < sizeof(array)/sizeof(array[0]); i++) fp(p, array[i]); } }; void comp_max(void *p, int i) { int *pmax = (int *)p; if (i > *pmax) *pmax = i; } void func(Collection *c) { int max = INT_MIN; c->apply(&max, comp_max); }The C way makes heavy use of pointers and casting. The casting is tedious, error prone, and loses all type safety.
class Collection { int[10] array; void apply(void delegate(int) fp) { for (int i = 0; i < array.length; i++) fp(array[i]); } } void func(Collection c) { int max = int.min; void comp_max(int i) { if (i > max) max = i; } c.apply(comp_max); } |
Pointers are eliminated, as well as casting and generic pointers. The D version is fully type safe. An alternate method in D makes use of function literals:
void func(Collection c) { int max = int.min; c.apply(delegate(int i) { if (i > max) max = i; } ); } |
eliminating the need to create irrelevant function names.