forked from john-b-schneider/uFDTD
-
Notifications
You must be signed in to change notification settings - Fork 0
/
fdtd-improved-code.tex
2127 lines (1839 loc) · 84.8 KB
/
fdtd-improved-code.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\chapter{Improving the FDTD Code \label{chap:fdtdImproved}}
%\setcounter{page}{1}
\renewcommand{\thefootnote}{\fnsymbol{footnote}}
\footnotetext{Lecture notes by John Schneider. {\tt
fdtd-improved-code.tex}}
\section{Introduction}
The C code presented in the previous chapter adequately served its
purpose---it implemented the desired FDTD functionality in a
relatively straightforward way. However, as the algorithms get more
involved, for instance, when implementing simulations of two- or
three-dimensional problems, the readability, portability, and
maintainability will be improved if our implementation is slightly
more sophisticated. In this chapter we will implement the algorithms
of the previous chapter in a way which may seem, at first, to be
overly complicated. However, as the complexity of the algorithms
increases in coming chapters, we will see that the effort required to
understand this more sophisticated approach will have been worth it.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Arrays and Dynamic Memory
Allocation \label{sec:memAllocation}}
\index{memory allocation|(}
In C an array is stored in a block of contiguous memory. Memory
itself is fundamentally a one-dimensional quantity since memory is
ultimately accessed with a single memory address. Let us consider a
one-dimensional array of doubles {\tt ez} where the size is specified
at run-time rather than at compile-time. The compiler needs to know
about the existence of this array, but at compile-time it does not
need to know the amount or the location of memory where the array will
be stored. We can specify the size of the array when the program is
run and we can allow the computer to decide the location of the
necessary amount of memory. The compiler is told about the potential
existence of the array with a pointer\index{pointers}. Once the
pointer is associated with the appropriate block of memory, it is
virtually indistinguishable from a one-dimensional array.
The code shown in Fragment \ref{frag:oneDDynamic} demonstrates how the
array {\tt ez} can be created at run-time. In line \ref{callocA} {\tt
ez} is declared as a pointer---it can store an address but when the
program initially starts it does not point to anything meaningful.
Line \ref{callocB} declares two integer variables: {\tt num\_elements}
which will contain the number of elements in the array, and {\tt mm}
which will be used as a loop counter. Lines \ref{callocC} and
\ref{callocD} determine the number of elements the user desires.
\begin{fragment}
Fragment of code demonstrating how the size of the array {\tt ez} can
be set at run-time. The header file {\tt stdlib.h} would typically
have to be included to provide the prototype for the {\tt calloc()}
function. \label{frag:oneDDynamic}
\codemiddle
\begin{lstlisting}
double *ez; /*@ \label{callocA} @*/
int num_elements, mm; /*@ \label{callocB} @*/
printf("Enter the size of the array: "); /*@ \label{callocC} @*/
scanf("%d", &num_elements); /*@ \label{callocD} @*/
ez = calloc(num_elements, sizeof(double)); /*@ \label{callocE} @*/
for (mm=0; mm < num_elements; mm++) /*@ \label{callocF} @*/
ez[mm] = 3.0 * mm; /*@ \label{callocG} @*/
\end{lstlisting}
\end{fragment}
Line \ref{callocE} is the key to getting {\tt ez} to behave as an
array. In this line the pointer {\tt ez} is set equal to the memory
address that is returned by the function {\tt calloc()}.\footnote{{\tt
calloc()} is closely related to the function {\tt malloc()} which also
allocates a block of memory and returns the address of the start of
that block. However {\tt calloc()} returns memory which has been
cleared, i.e., set to zero, while {\tt malloc()} returns memory which
may contain anything. Since we want the field arrays initially to be
zero, it is better to use {\tt calloc()} than {\tt malloc()}.}
\index{calloc@{\tt calloc()}} {\tt calloc()} takes two arguments. The
first specifies the number of elements in the array while the
second specifies the amount of memory needed for a single
element. Here the {\tt sizeof()} operator is used to obtain the size
of a double variable. (Although not shown in this fragment, when
using the {\tt calloc()} function one typically has to include the
header file {\tt stdlib.h} to provide the function prototype.) If
{\tt calloc()} is unable to provide the requested memory it will
return {\tt NULL}.
\footnote{Robust code would check the return value and take appropriate
measures if {\tt NULL} were returned. For now we will assume that
{\tt calloc()} succeeded.}
After the call of {\tt calloc()} in line \ref{callocE}, {\tt ez}
points to the start of a contiguous block of memory where the array
elements can be stored. To demonstrate this, lines \ref{callocF} and
\ref{callocG} write the value of three times the array index to each
element of the array (so that {\tt ez[0]} would be $0.0$, {\tt ez[1]}
would be $3.0$, {\tt ez[2]} would be $6.0$, and so on).
\index{memory allocation|)}
Some compilers will actually complain about the code as it is written
in Fragment \ref{frag:oneDDynamic}. The ``problem'' is that
technically {\tt calloc()} returns a void pointer---it is simply the
address of the start of a block of memory, but we have not said what is
stored at that memory. We want a pointer to doubles since we will be
storing double precision variables in this memory. The compiler
really already knows this since we are storing the address in {\tt ez}
which is a pointer to doubles. Nevertheless, some compilers will give
a warning because of line \ref{callocE}. Therefore, to ensure that
compilers do not complain, it would be best to replace line
\ref{callocE} with
\begin{code}
ez = (double *)calloc(num_elements, sizeof(double));
\end{code}
In this way the void pointer returned by {\tt calloc()} is converted
(or cast) to a pointer to doubles.
\section{Macros \label{sec:macros}}
\index{macros|(}
C provides a preprocessor which ``processes'' your code prior to the
compiler itself. Preprocessor directives start with the pound sign
({\tt \#}) and instruct the preprocessor to do things such as
include a header file (with an {\tt \#include} statement) or
substitute one string for another. Program \ref{pro:1DbareBones}
had three preprocessor directives. Two were used to include the files
{\tt stdio.h} and {\tt math.h} and one used a {\tt \#define} statement
to tell the preprocessor to substitute {\tt 200} for all occurrences
of the string {\tt SIZE}.
Compilers allow you to see what the source code is after passing
through the preprocessor. Using the GNU C compiler, one adds the {\tt
-E} flag to the compiler command to obtain the output from the
preprocessor. So, for example, one can see the source code as it
appears after passing through the preprocessor with the command
\index{gcc!preprocessor output}
\begin{code}
gcc -E 1DbareBones.c
\end{code}
In this case you will observe that there are many, many more lines of
output than there are in your original program. This is because of
the inclusion of the header files. Your original program will appear
at the end of the output except now {\tt SIZE} does not appear
anywhere. Instead, any place it had appeared you will now see {\tt
200}.
The {\tt \#define} statement can also be used to create a macro.
Unlike the simple string substitution used before, a macro can take
one or more arguments. These arguments dictate how strings given as
arguments should re-appear in the output. For example, consider the
following macro
\begin{code}
#define SQR(X) ((X) * (X))
\end{code}
This tells the preprocessor that every time {\tt SQR} appears in the
source code, the preprocessor should take whatever appeared as an
argument and replace that with the argument multiplied by itself.
Here the argument {\tt X} is just serving as a place holder. Consider
the following code
\begin{code}
a = 6.0 * SQR(3.0 + 4.0);
\end{code}
After passing through the preprocessor, this code would appear as
\begin{code}
a = 6.0 * ((3.0 + 4.0) * (3.0 + 4.0));
\end{code}
The result would be that {\tt a} would be set equal to $6\times7^2=294$.
It may seem that there are an excess number of parentheses in this
macro, but one must be careful with macros to ensure the desired
results are obtained. Consider this macro
\begin{code}
#define BAD_SQR(X) X * X
\end{code}
If a program then contained this statement
\begin{code}
a = 6.0 * BAD_SQR(3.0 + 4.0);
\end{code}
the preprocessor translates this to
\begin{code}
a = 6.0 * 3.0 + 4.0 * 3.0 + 4.0;
\end{code}
Since multiplication has a higher precedence than addition, in this
case {\tt a} would be set equal to $18+12+4=34$. There is no harm in
using extra parentheses, so do not hesitate to use them to ensure
you are getting precisely what you want.
It is also worth noting that the macro definition itself will
typically not be terminated by a semicolon. If one includes a
semicolon, it could easily produce unintended results. As an
example, consider
\begin{code}
#define BAD_SQR1(X) ((X) * (X));
\end{code}
If a program then contained this statement
\begin{code}
a = BAD_SQR1(4.0) + 10.0;
\end{code}
the preprocessor would translate this to
\begin{code}
a = ((4.0) * (4.0)); + 10;
\end{code}
Note that the ``{\tt + 10}'' is consider a separate statement since
there is a semicolon between it and the squared term.\footnote{When
using the GNU C compiler, this ``bad'' code will compile without
error. If one adds the {\tt -Wall} flag when compiling, the GNU
compiler will provide a warning that gives the line number and a
statement such as ``{\tt warning: statement with no effect}.''
Nevertheless, the code will compile.} Thus, {\tt a} will be set to
$16.0$.
Macros may have any number of arguments. As an example, consider
\begin{code}
#define FOO(X, Y) (Y) * cos((X) * (Y))
\end{code}
Given this macro, the following code
\begin{code}
a = FOO(cos(2.15), 7.0 * sqrt(4.0));
\end{code}
would be translated by the preprocessor to
\begin{code}
a = (7.0 * sqrt(4.0)) * cos((cos(2.15)) * (7.0 * sqrt(4.0)))
\end{code}
Macros can span multiple lines provided the newline character at the
end of each line is ``quoted'' with a backslash. Said another way, a
backslash must be the last character on a line if the statement is to
continue on the next line.
There is another ``trick'' that one can do with macros. One can say
they want the string version of an argument in the preprocessor
output, essentially the argument will appear enclosed in quotes in the
output. To understand why this is useful, it first helps to recall
that in C, if one puts two string next to each other, the compiler
treats the two separate strings as a single string. Thus, the
following commands are all equivalent
\begin{code}
printf("Hello world.\n");
printf("Hello " "world.\n");
printf("Hello "
"world.\n");
\end{code}
Each of these produce the output
\begin{code}
Hello world.
\end{code}
Note that there is no comma between the separated strings in the {\tt
printf()} statements and the amount of whitespace between the strings
is irrelevant.
If we want the preprocessor to produce the string version of an
argument in the output, we affix {\tt \#} to the argument name in the
place where it should appear in the output. For example, we could use
a macro to print what is being calculated and show the result of the
calculation as well:
\begin{code}
#define SHOW_CALC(X) \
printf(#X " = %g\n", X)
\end{code}
The first line of this macro is terminated with a backslash telling
the preprocessor that the definition is continued on the next line.
Now, if the code contained
\begin{code}
SHOW_CALC(6.0 + 24.0 / 8.0);
\end{code}
the preprocessor would convert this to
\begin{code}
printf("6.0 + 24.0 / 8.0" " = %g\n", 6.0 + 24.0 / 8.0);
\end{code}
When the program is run, the following would be written to the screen
% NOTE: the following is correct -- the "9" is printed without a
% trailing dot even though it is a float.
\begin{code}
6.0 + 24.0 / 8.0 = 9
\end{code}
We are now at a point where we can construct a fairly sophisticated
macro to do memory allocation. The macro will check if the allocation
of memory was successful. If not, the macro will report the problem
and terminate the program. The following fragment shows the desired
code.
\begin{fragment}
Macro for allocating memory for a one-dimensional array. The trailing
backslashes must appear directly before the end of the line. (By
``quoting'' the newline character at the end of the line we are
telling the preprocessor the definition is continued on the next
line.)
\label{frag:allocMacro}
\codemiddle
\begin{lstlisting}
#define ALLOC_1D(PNTR, NUM, TYPE) \
PNTR = (TYPE *)calloc(NUM, sizeof(TYPE)); \
if (!PNTR) { \
perror("ALLOC_1D"); \
fprintf(stderr, \
"Allocation failed for " #PNTR ". Terminating...\n");\
exit(-1); \
}
\end{lstlisting}
\end{fragment}
The macro {\tt ALLOC\_1D()} takes three arguments: a pointer (called
{\tt PNTR}), an integer specifying the number of elements ({\tt NUM}),
and a data type ({\tt TYPE}). The macro uses {\tt calloc()} to obtain
the desired amount of memory. It then checks if the allocation was
successful. Recall that {\tt calloc()} will return {\tt NULL} if it
failed to allocate the requested memory. In C, the exclamation mark
is also the ``not operator.'' So, if the value of {\tt PNTR} is {\tt
NULL} (or, thought of another way, ``false''), then {\tt !PNTR}
would be true. If the memory allocation failed, two error messages
are printed (one message is generated using the system function {\tt
perror()}, the other uses {\tt fprintf()} and writes the output to
{\tt stderr}, which is typically the screen). The program is then
terminated with the {\tt exit()} command.
With this macro in our code we could create an array {\tt ez} with
$200$ elements with statements such as
\begin{code}
double *ez;
ALLOC_1D(ez, 200, double);
\end{code}
Technically the semicolon in the second statement is not necessary
(since this macro translates to a block of code that ends with a
close-brace), but there is no harm in having it.
\index{macros|)}
\section{Structures}
\index{struct|see{structures}}
\index{structures}
In C one can group data together, essentially create a compound data
type, using what is known as a structure. A program can have
different types of structures, i.e., ones that bundle together
different kinds of data. For example, a program might use structures
that bundle together a person's age, weight, and name as well as
structures that bundle together a person's name, social security
number, and income. Just as we can have multiple variables of a given
type and each variable is a unique instance of that data type, we can
have multiple variables that corresponds to a given structure, each of
which is a unique instance of that structure.
Initially, when declaring a structure, we first tell the compiler the
composition of the structure. As an example, the following command
defines a {\tt person} structure that contains three elements: a
character pointer called {\tt name} that will be used to store the
name of a person, an integer called {\tt age} that will correspond to
the person's age, and an integer called {\tt weight} that will
correspond to the person's weight.
\begin{code}
struct person {
char *name;
int age;
int weight;
};
\end{code}
This statement is merely a definition---no structure has been created
yet. To create a {\tt person} structure called {\tt bob} and another
one called {\tt sue}, a command such as the following could be
used:
\begin{code}
struct person bob, sue;
\end{code}
Actually, one could combine these two statements and create {\tt bob}
and {\tt sue} with the following:
\begin{code}
struct person {
char *name;
int age;
int weight;
} bob, sue;
\end{code}
However, in the code to come, we will use a variant of the
two-statement version of creating structures. It should also be
pointed out that elements do not need to be declared with individual
statements of their own. For example, we could write
\begin{code}
int age, weight;
\end{code}
instead of the two separate lines shown above.
To access the elements of a structure we write the name of the
structure, a dot, and then the name of the element. For example, {\tt
bob.age} would be the {\tt age} element of the structure {\tt bob},
and {\tt sue.age} would be the {\tt age} element of the structure {\tt
sue} (despite the fact that these are both {\tt age} elements, they
are completely independent variables).
Now, let's write a program that creates two {\tt person} structures.
The program has a function called {\tt showPerson1()} to show the
elements of a {\tt person} and another function called {\tt
changePerson1()} that reduces the person's age by two and decreases
the weight by five. Each of these functions takes a single
argument, a {\tt person} structure. The program is shown in Program
\ref{pro:structDemo1}.
\begin{program} {\tt struct-demo1.c}: Program to demonstrate the basic
use of structures.
\label{pro:structDemo1}
\codemiddle
\begin{lstlisting}
/* Program to demonstrate the use of structures. Here structures are
passed as arguments to functions.*/
#include <stdio.h>
struct person {
char *name;
int age;
int weight;
};
void changePerson1(struct person p);
void showPerson1(struct person p);
int main() {
struct person bob, sue; /*@ \label{structDemo1A} @*/
sue.name = "Sue"; /*@ \label{structDemo1B} @*/
sue.age = 21;
sue.weight = 120;
bob.name = "Bob"; /*@ \label{structDemo1C} @*/
bob.age = 62;
bob.weight = 180;
showPerson1(sue); /*@ \label{structDemo1D} @*/
printf("*** Before changePerson1() ***\n");
showPerson1(bob); /*@ \label{structDemo1E} @*/
changePerson1(bob); /*@ \label{structDemo1F} @*/
printf("*** After changePerson1() ***\n");
showPerson1(bob); /*@ \label{structDemo1G} @*/
return 0;
}
/* Function to display the elements in a person. */
void showPerson1(struct person p) { /*@ \label{structDemo1H} @*/
printf("name: %s\n", p.name);
printf("age: %d\n", p.age);
printf("weight: %d\n", p.weight);
return;
}
/* Function to modify the elements in a person. */
void changePerson1(struct person p) { /*@ \label{structDemo1I} @*/
p.age = p.age - 2;
p.weight = p.weight - 5;
printf("*** In changePerson1() ***\n");
showPerson1(p); /*@ \label{structDemo1J} @*/
return;
}
\end{lstlisting}
\end{program}
In line \ref{structDemo1A} of the {\tt main()} function we declare the
two {\tt person} structures {\tt bob} and {\tt sue}. This allocates
the space for these structures, but as yet their elements do not have
meaningful values. In \ref{structDemo1B} we set the {\tt name} of
element of {\tt sue}. The following two lines set the {\tt age} and {\tt
weight}. The elements for {\tt bob} are set starting at line
\ref{structDemo1C}. In line \ref{structDemo1D} the {\tt showPerson1()}
function is called with an argument of {\tt sue}. This function,
which starts on line \ref{structDemo1H}, shows the {\tt name}, {\tt
age}, and {\tt weight} of a {\tt person}.
After showing the elements of {\tt sue}, in line \ref{structDemo1E} of
{\tt main()}, {\tt showPerson1()} is used to show the elements of {\tt
bob}. In line \ref{structDemo1F} the function {\tt changePerson1()}
is called with an argument of {\tt bob}. This function, which is
given starting on line \ref{structDemo1I}, subtracts two from the {\tt
age} and five from the {\tt weight}. After making these
modifications, in line \ref{structDemo1J}, {\tt showPerson1()} is
called to display the modified person.
Finally, returning to line \ref{structDemo1G} of the {\tt main()}
function, the {\tt showPerson1()} function is called once again with an
argument of {\tt bob}. Note that this is after {\tt bob} has
supposedly been modified by the {\tt changePerson1()} function. The
output produced by Program \ref{pro:structDemo1} is
\begin{code}
name: Sue
age: 21
weight: 120
*** Before changePerson1() ***
name: Bob
age: 62
weight: 180
*** In changePerson1() ***
name: Bob
age: 60
weight: 175
*** After changePerson1() ***
name: Bob
age: 62
weight: 180
\end{code}
Here we see that the initial values of {\tt sue} and {\tt bob} are as
we would anticipate. Also, when {\tt showPerson1()} is called from
within {\tt changePerson1()}, we see the modified values for {\tt bob},
i.e., his age has been reduced by two and his weight has been reduced
by five. {\em However,} the last three lines of output show that,
insofar as the {\tt bob} structure in the {\tt main()} function is
concerned, nothing has changed! {\tt bob} has not been modified by
{\tt changePerson1()}.
Although this behavior may not have been what we would have
anticipated, it is correct. When a structure is given as an argument,
the function that is called is given a complete copy of the original
structure. Thus, when that function modifies the elements of the
structure, it is modifying a copy of the original---it is not
affecting the original itself.
If we want a function that we call to be able to modify a structure,
we must pass a pointer to that structure. We will modify the code to
permit this but let use first introduce the {\tt typedef} statement
that allows to write slightly cleaner code. Having to write {\tt
struct person} everywhere we want to specify a {\tt person}
structure is slightly awkward. C allows us to use the {\tt typedef}
statement to define an equivalent. The following statement tells the
compiler that {\tt Person} is the equivalent of {\tt struct person}
\begin{code}
typedef struct person Person;
\end{code}
Note that there is no requirement that we use the same word for the
structure and the {\tt typedef}-equivalent. Additionally, even when
using the same word, there is no need to use different capitalization
(since the structure and its equivalent are maintained in different
name spaces).
Now, let us assume we want to create two {\em pointers} to structures,
one named {\tt susan} and one {\tt robert}. These can be created with
\begin{code}
struct person *susan, *robert;
\end{code}
Assuming the {\tt typedef} statement given above has already appeared
in the program, we could instead write:
\begin{code}
Person *susan, *robert;
\end{code}
{\tt susan} and {\tt robert} are pointers to structures but,
initially, they do not actually point to any structures. We cannot
set their elements because there is is no memory allocated for the
storage of these elements. Thus, we must allocate memory for these
structures and ensure the pointers point to the memory.
To accomplish this, we can include in our program a statement such as
\begin{code}
ALLOC_1D(susan, 1, Person);
\end{code}
Recalling the {\tt ALLOC\_1D()} macro presented in Sec.\
\ref{sec:macros}, this statement will allocate the memory for one {\tt
Person} and associate that memory with the pointer {\tt susan}. We
can now set the element associated with {\tt susan}. However,
accessing the elements of a {\em pointer} to a {\tt Person} is different
than directly accessing the elements of a {\tt Person}. To set the
{\tt age} element of {\tt susan} we would have to write either
\begin{code}
(*susan).age = 21;
\end{code}
or
\begin{code}
susan->age = 21;
\end{code}
Program \ref{pro:structDemo2} is similar to Program
\ref{pro:structDemo1} in many respects except here, rather than using
structures directly, the program primarily deals with pointers to
structures. As will be shown, this allows other functions to change
the elements within any given structure---the function merely has to
be passed a pointer to the structure rather than (a copy of) the
structure.
\begin{program} {\tt struct-demo2.c}: Program to demonstrate the basic
use of pointers to structures.
\label{pro:structDemo2}
\codemiddle
\begin{lstlisting}
/* Program to demonstrate the use of pointers to structures. */
#include <stdlib.h>
#include <stdio.h>
#define ALLOC_1D(PNTR, NUM, TYPE) \
PNTR = (TYPE *)calloc(NUM, sizeof(TYPE)); \
if (!PNTR) { \
perror("ALLOC_1D"); \
fprintf(stderr, \
"Allocation failed for " #PNTR ". Terminating...\n");\
exit(-1); \
}
struct person {
char *name;
int age;
int weight;
};
typedef struct person Person; /*@ \label{structDemo2A} @*/
void changePerson2(Person *p);
void showPerson2(Person *p);
int main() {
Person *robert, *susan; /*@ \label{structDemo2B} @*/
ALLOC_1D(susan, 1, Person); /*@ \label{structDemo2C} @*/
ALLOC_1D(robert, 1, Person);
susan->name = "Susan";
susan->age = 21;
susan->weight = 120;
robert->name = "Robert";
robert->age = 62;
robert->weight = 180;
showPerson2(susan);
printf("*** Before changePerson2() ***\n");
showPerson2(robert);
changePerson2(robert);
printf("*** After changePerson2() ***\n");
showPerson2(robert);
return 0;
}
/* Function to display the elements in a person. */
void showPerson2(Person *p) {
printf("name: %s\n", p->name);
printf("age: %d\n", p->age);
printf("weight: %d\n", p->weight);
return;
}
/* Function to modify the elements in a person. */
void changePerson2(Person *p) {
p->age = p->age - 2;
p->weight = p->weight - 5;
printf("*** In changePerson2() ***\n");
showPerson2(p);
return;
}
\end{lstlisting}
\end{program}
The {\tt typedef} statement in line \ref{structDemo2A} allows us to
write simply {\tt Person} instead of {\tt struct person}. This is
followed by the prototypes for functions {\tt showPerson2()} and {\tt
changePerson2()}. These functions are similar to the corresponding
functions in the previous program except now the arguments are
pointers to structures instead of structures. Thus, the syntactic
changes are necessary in the functions themselves (e.g., we have to
write \verb+p->age+ instead of \verb+p.age+). Starting on line
\ref{structDemo2C} the {\tt ALLOC\_1D()} macro is used to allocate the
memory for the {\tt susan} and {\tt robert} pointers that were
declared in line \ref{structDemo2B}. The values of the elements are
then set, the contents of the structures displayed, {\tt robert} is
modified, and the contents or {\tt robert} are shown again.
The output produced by
Program \ref{pro:structDemo2} is
\begin{code}
name: Susan
age: 21
weight: 120
*** Before changePerson2() ***
name: Robert
age: 62
weight: 180
*** In changePerson2() ***
name: Robert
age: 60
weight: 175
*** After changePerson2() ***
name: Robert
age: 60
weight: 175
\end{code}
Note that in this case, the changes made by {\tt changePerson2()} are
persistent---when the {\tt main()} function shows the elements of {\tt
robert} we see the modified values (unlike with the previous
program).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Improvement Number One \label{sec:improveOne}}
Now let us use some of the features discussed in the previous sections
in a revised version of the ``bare-bones'' program that appeared in
Sec.\ \ref{sec:bareBones}. Here we will bundle much of the data
associated with an FDTD simulation into a structure that we will call
a {\tt Grid} structure. (We will often refer to this structure as
simply the {\tt Grid} or a {\tt Grid}.) Here will define the {\tt
Grid} as follows:
\begin{code}
struct Grid {
double *ez; // electric field array
double *hy; // magnetic field array
int sizeX; // size of computational domain
int time, maxTime; // current and max time step
double cdtds; // Courant number
};
\end{code}
In the ``improved'' program that we will soon see, there will not
appear to be much reason for creating this structure. Why bother?
The motivation will be more clear when we start to modularize the code
so that different functions handle different aspects of the FDTD
simulation. By bundling all the relevant information about the
simulation into a structure, we can simply pass as an argument to each
function a pointer to this {\tt Grid}.
First, let us create a header file {\tt fdtd1.h} in which we will put
the definition of a {\tt Grid} structure. In this file we will also
include the (1D) macro for allocating memory. Finally, in a quest to
keep the code as clean as possible, we will also define a few additional
preprocessor directives: a macro for accessing the {\tt ez} array elements,
a macro for accessing the {\tt hy} array elements, and some simple {\tt
\#define} statements to facilitate references to {\tt sizeX}, {\tt
time}, {\tt maxTime}, and {\tt cdtds}. The complete header file is
shown in Program \ref{pro:impfdtd1}.
\begin{program}
{\tt fdtd1.h}:
Header file for the first ``improved'' version of a simple 1D FDTD
program. \label{pro:impfdtd1}
\codemiddle
\begin{lstlisting}
#ifndef _FDTD1_H /*@ \label{impfdtd1A} @*/
#define _FDTD1_H /*@ \label{impfdtd1B} @*/
#include <stdio.h>
#include <stdlib.h>
struct Grid { /*@ \label{impfdtd1C} @*/
double *ez;
double *hy;
int sizeX;
int time, maxTime;
double cdtds;
};
typedef struct Grid Grid;
/* memory allocation macro */
#define ALLOC_1D(PNTR, NUM, TYPE) /*@ \label{impfdtd1D} @*/\
PNTR = (TYPE *)calloc(NUM, sizeof(TYPE)); \
if (!PNTR) { \
perror("ALLOC_1D"); \
fprintf(stderr, \
"Allocation failed for " #PNTR ". Terminating...\n");\
exit(-1); \
}
/* macros for accessing arrays and such */
/* NOTE!!!! Here we assume the Grid structure is g. */
#define Hy(MM) g->hy[MM] /*@ \label{impfdtd1E} @*/
#define Ez(MM) g->ez[MM] /*@ \label{impfdtd1F} @*/
#define SizeX g->sizeX /*@ \label{impfdtd1G} @*/
#define Time g->time
#define MaxTime g->maxTime
#define Cdtds g->cdtds /*@ \label{impfdtd1K} @*/
#endif /* matches #ifndef _FDTD1_H */ /*@ \label{impfdtd1L} @*/
\end{lstlisting}
\end{program}
Line \ref{impfdtd1A} checks if this header file, i.e., {\tt fdtd1.h},
has previously been included. Including multiple copies of a header
file can cause errors or warnings (such as when a term that was
previously defined in a {\tt \#define} statement is again mentioned in
a different {\tt \#define} statement). In the simple code with which
we are working with now, multiple inclusions are not a significant
concern, but we want to develop the techniques to ensure multiple
inclusions do not cause problems in the future. Line \ref{impfdtd1A}
checks for a previous inclusion by testing if the identifier
(technically a compiler directive) {\tt \_FDTD1\_H} is not defined.
If it is not defined, the next statement on line \ref{impfdtd1B}
defines it. Thus, {\tt \_FDTD1\_H} serves as something of a flag. It
will be defined if this file has been previously included and will not
be defined otherwise. Therefore, owing to the {\tt \#ifndef}
statement at the start of the file, if this header file has previously
been included the preprocessor will essentially ignore the rest of the
file. The {\tt \#ifndef} statement on line \ref{impfdtd1A} is paired
with the {\tt \#endif} statement on line \ref{impfdtd1L} at the end of
the file.
Assuming this file has not previously been included, next,
the header files {\tt stdio.h} and {\tt stdlib.h} are included. Since
the macro {\tt ALLOC\_1D()} uses both {\tt calloc()} and some printing
functions, both these headers have to be included anywhere {\tt
ALLOC\_1D()} is used.
The commands for defining the {\tt Grid} structure start on line
\ref{impfdtd1C}. Following that, the {\tt ALLOC\_1D()} macro begins
on line \ref{impfdtd1D}. The macros given on lines \ref{impfdtd1E}
and \ref{impfdtd1F} allow us to access the field arrays in a way that
is easier to read. (Importantly, note that these statements assume
that the {\tt Grid} in the program has been given the name {\tt g}!
We will, in fact, make a habit of using the variable name {\tt g} for
a {\tt Grid} in the code to follow.) So, for example, the macro on
line \ref{impfdtd1F} allows us to write {\tt Ez(50)} instead of
writing the more cumbersome \verb+g->ez[50]+. Note that the index for
the array element is now enclosed in parentheses and not square
brackets. The definitions in lines \ref{impfdtd1G} through
\ref{impfdtd1K} allow us to write {\tt SizeX} instead of {\tt
g->sizeX}, {\tt Time} instead of {\tt g->time}, {\tt MaxTime}
instead of {\tt g->maxTime}, and {\tt Cdtds} instead of {\tt
g->cdtds}.
An improved version of Program \ref{pro:1DbareBones} is shown in
Program \ref{pro:improved1}.
\begin{program} {\tt improved1.c}: Source code for an improved version
of the bare-bones 1D FDTD program. \label{pro:improved1} \codemiddle
\begin{lstlisting}
/* Improved bare-bones 1D FDTD simulation. */
#include "fdtd1.h"
#include <math.h>
int main()
{
Grid *g; /*@ \label{improved1A} @*/
double imp0 = 377.0;
int mm;
ALLOC_1D(g, 1, Grid); /*@ \label{improved1F} @*/
SizeX = 200; // size of grid /*@ \label{improved1B} @*/
MaxTime = 250; // duration of simulation /*@ \label{improved1E} @*/
Cdtds = 1.0; // Courant number (unused)
ALLOC_1D(g->ez, SizeX, double); /*@ \label{improved1C} @*/
ALLOC_1D(g->hy, SizeX, double); /*@ \label{improved1D} @*/
/* do time stepping */
for (Time = 0; Time < MaxTime; Time++) {
/* update magnetic field */
for (mm = 0; mm < SizeX - 1; mm++)
Hy(mm) = Hy(mm) + (Ez(mm + 1) - Ez(mm)) / imp0;
/* update electric field */
for (mm = 1; mm < SizeX; mm++)
Ez(mm) = Ez(mm) + (Hy(mm) - Hy(mm - 1)) * imp0;
/* hardwire a source node */
Ez(0) = exp(-(Time - 30.0) * (Time - 30.0) / 100.0);
printf("%g\n", Ez(50));
} /* end of time-stepping */
return 0;
}
\end{lstlisting}
\end{program}
In line \ref{improved1A} the pointer to the {\tt Grid} structure {\tt
g} is declared. Because we have just declared a pointer to a {\tt
Grid}, we next need to obtain the memory to store the elements of
the structure itself. This allocation is done in line \ref{improved1F}.
Because the {\tt Grid} contains pointers {\tt ez} and {\tt hy}, we do
not need to declare those field arrays separately. The size of the
computational domain is set in line \ref{improved1B} while the
duration of the simulation is set in \ref{improved1E}. After the
preprocessor has processed the code, these lines will actually be
\begin{code}
g->sizeX = 200;
g->maxTime = 250;
\end{code}
At this point in the program the pointers {\tt ez} and {\tt hy} do not
have any memory associated with them---we cannot store anything yet in
the field arrays. Therefore the next step is the allocation of memory
for the field arrays which is accomplished in lines \ref{improved1C}
and \ref{improved1D}.
The rest of the code is largely the same as given in Program
\ref{pro:1DbareBones}. The only difference being a slight change in
notation/syntax. Program \ref{pro:1DbareBones} and Program
\ref{pro:improved1} produce identical results.
This new version of the bare-bones simulation is split into two files:
the header file {\tt fdtd1.h} and the source file {\tt improved1.c}.
This is depicted in Fig.\ \ref{fig:improved1Files}.
\begin{figure}
\begin{center}
\epsfig{width=1.1in,file=Figures/Fdtd-improved-code/improved1-files.eps}
\end{center} \caption{The files associated with the first improved
version of the FDTD code. The header file {\tt fdtd1.h} is included
in the source file {\tt improved1.c} as indicated by the dashed
line. The source file {\tt improved1.c} contains the {\tt main()}
function which performs all the executable statements associated with
the simulation.} \label{fig:improved1Files}
\end{figure}
The {\tt main()} function in file {\tt improved1.c} handles all the
calculations associated with the FDTD simulation. Since there is only
a single source file, there is no obvious advantage to creating a
header file. However, as we further modularize the code, the header
file will provide a convenient way to ensure that different source
files share common definitions of various things. For example, many
source files may need to know about the details of a {\tt Grid}
structure. These details can be written once in the header file and
then the header file can be included into different source files as
appropriate. Examples of this will be provided in the following
sections.
\section{Modular Design and Initialization Functions\label{sec:modularDesign}}
Thus far all the programs have been written using a single source file
that contains a single function (the {\tt main()} function), or in the
case of the previous section, one source file and one header file. As
the complexity of the FDTD simulation increases this approach becomes
increasingly unwieldy and error prone. A better approach is to
modularize the program so that different functions perform specific
tasks---not everything is done within {\tt main()}. For example, we
may want to use one function to update the magnetic field, another to
update the electric field, another to introduce energy into the grid,
and another to handle the termination of the grid.
Additionally, with C it is possible to put different functions in
different source files and compile the source files separately. By
putting different functions in different source files, it is possible
to have different functions that are within one source file share
variables which are ``hidden'' from all functions that do not appear
in that file. You can think of these variables as being private,
known only to the functions contained in the file. As will be shown,
such sharing of variables can be useful when one wants to initialize
the behavior of a function that will be called multiple times (but the
initialization only needs to be done once).
To illustrate how functions within a file can share variables that are
otherwise hidden from other parts of a program, assume there is a
function that we want to call several times. Further assume this
function performs a calculation based on some parameters but these
parameters only need to be set once---they will not vary from the
value to which they are initially set. For this type of scenario, it
is often convenient to split the function into two functions: one
function handles the initialization of the parameters (the
``initialization function'') and the other function handles the
calculation based on those parameters (the ``calculation function'').
Now, the question is: How can the initialization function make the
parameters visible to the calculation function and how can the values
of these parameters persist between one invocation of the function and
the next? The answer lies in global variables.
Generally the use of global variables is discouraged as they can make
programs hard to understand and difficult to debug. However, global
variables can be quite useful when used properly. To help minimize
the problems associated with global variables, we will further
modularizing the program so that the initialization function and
calculation function mentioned above are stored in a separate file
from the rest of the program. In this way the global variables that
these functions share are not visible to any other function.
As a somewhat contrived example of this approach to setting
parameters, assume we want to write a program that will calculate the
values of a harmonic function where the user can specify the amplitude
and the phase, i.e., we want to calculate $f(x) = a\cos(x+\phi)$ where
$a$ is the amplitude and $\phi$ is the phase. Note that we often just
write $f(x)$ for a function like this even though the function depends
on $a$ and $\phi$ as well as $x$. We usually do {\em not} write
$f(x,a,\phi)$ because we think of $a$ and $\phi$ as fixed values (even
though they have to be specified at some point) while $x$ is the value
we are interested in varying. Assume we want to write our program so
that there is a function {\tt harmonic1()} that is the equivalent of
$f(x) = a\cos(x+\phi)$. {\tt harmonic1()} should take a single
argument that corresponds to the value of $x$. We will use a separate
function, {\tt harmonicInit1()} that will set the amplitude and phase.
A file that contains a suitable the {\tt main()} function and the
associated statements to realize the parameter setting discussed above
is shown in Program \ref{pro:paramDemo1}. The function prototypes for
{\tt harmonicInit1()} and {\tt harmonic1()} are given in lines
\ref{paramDemo1A} and \ref{paramDemo1B}, respectively. Note, however,
that these functions do not appear in this file. Between lines
\ref{paramDemo1C} and \ref{paramDemo1D} the user is prompted for the
amplitude and phase (and the phase is converted from degrees to
radians). These values are passed as arguments to the {\tt
harmonicInit1()} function. As we will see a little later, this
function sets persistent global parameters to these values so that
they are visible to the function {\tt harmonic1()} whenever it is
called. The for-loop that starts on line \ref{paramDemo1F} generates
the desired output values. Here we set the variable {\tt x} to values
between $0$ and $2\pi$. In line \ref{paramDemo1G} the value of {\tt x}
is printed together with the value of {\tt harmonic1(x)}. Note that
the {\tt harmonic1()} function is not passed the amplitude or phase as
an argument.
\begin{program}
{\tt param-demo1.c}: File containing the {\tt main()} function and
appropriate header material that is used to demonstrate the setting of
persistent parameters in an auxiliary function. Here {\tt
harmonicInit1()} and {\tt harmonic1()} are serving this auxiliary
role. The code associated with those functions is in a separate
file (see Program \ref{pro:harmonicDemo1}).
\label{pro:paramDemo1}