-
Notifications
You must be signed in to change notification settings - Fork 12
/
pointers.d
1653 lines (1241 loc) · 46.9 KB
/
pointers.d
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
Ddoc
$(DERS_BOLUMU $(IX pointer) Pointers)
$(P
Pointers are variables that provide access to other variables. The value of a pointer is the address of the variable that it provides access to.
)
$(P
Pointers can point at any type of variable, object, and even other pointers. In this chapter, I will refer to all of these simply as $(I variables).
)
$(P
Pointers are low level features of microprocessors. They are an important part of system programming.
)
$(P
The syntax and semantics of pointers in D are inherited directly from C. Although pointers are notoriously the most difficult feature of C to comprehend, they should not be as difficult in D. This is because other features of D that are semantically close to pointers are more useful in situations where pointers would have to be used in other languages. When the ideas behind pointers are already understood from those other features of D, pointers should be easier to grasp.
)
$(P
The short examples throughout the most of this chapter are decidedly simple. The programs at the end of the chapter will be more realistic.
)
$(P
The names like $(C ptr) (short for "pointer") that I have used in these examples should not be considered as useful names in general. As always, names must be chosen to be more meaningful and explanatory in actual programs.
)
$(H5 $(IX reference, concept) The concept of a reference)
$(P
Although we have encountered references many times in the previous chapters, let's summarize this concept one more time.
)
$(H6 The $(C ref) variables in $(C foreach) loops)
$(P
As we have seen in $(LINK2 /ders/d.en/foreach.html, the $(C foreach) Loop chapter), normally the loop variables are $(I copies) of elements:
)
---
import std.stdio;
void main() {
int[] numbers = [ 1, 11, 111 ];
foreach (number; numbers) {
number = 0; // ← the copy changes, not the element
}
writeln("After the loop: ", numbers);
}
---
$(P
The $(C number) that gets assigned 0 each time is a copy of one of the elements of the array. Modifying that copy does not modify the element:
)
$(SHELL
After the loop: [1, 11, 111]
)
$(P
When the actual elements need to be modified, the $(C foreach) variable must be defined as $(C ref):
)
---
foreach ($(HILITE ref) number; numbers) {
number = 0; // ← the actual element changes
}
---
$(P
This time $(C number) is a reference to an actual element in the array:
)
$(SHELL_SMALL
After the loop: [0, 0, 0]
)
$(H6 $(C ref) function parameters)
$(P
As we have seen in $(LINK2 /ders/d.en/function_parameters.html, the Function Parameters chapter), the parameters of $(I value types) are normally copies of the arguments:
)
---
import std.stdio;
void addHalf(double value) {
value += 0.5; // ← Does not affect 'value' in main
}
void main() {
double value = 1.5;
addHalf(value);
writeln("The value after calling the function: ", value);
}
---
$(P
Because the function parameter is not defined as $(C ref), the assignment inside the function affects only the local variable there. The variable in $(C main()) is not affected:
)
$(SHELL_SMALL
The value after calling the function: 1.5
)
$(P
The $(C ref) keyword would make the function parameter a reference to the argument:
)
---
void addHalf($(HILITE ref) double value) {
value += 0.5;
}
---
$(P
This time the variable in $(C main()) gets modified:
)
$(SHELL_SMALL
The value after calling the function: 2
)
$(H6 Reference types)
$(P
Some types are reference types. Variables of such types provide access to separate variables:
)
$(UL
$(LI Class variables)
$(LI Slices)
$(LI Associative arrays)
)
$(P
We have seen this distinction in $(LINK2 /ders/d.en/value_vs_reference.html, the Value Types and Reference Types chapter). The following example demonstrates reference types by two $(C class) variables:
)
---
import std.stdio;
class Pen {
double ink;
this() {
ink = 15;
}
void use(double amount) {
ink -= amount;
}
}
void main() {
auto pen = new Pen;
auto otherPen = pen; // ← Now both variables provide
// access to the same object
writefln("Before: %s %s", pen.ink, otherPen.ink);
pen.use(1); // ← the same object is used
otherPen.use(2); // ← the same object is used
writefln("After : %s %s", pen.ink, otherPen.ink);
}
---
$(P
Because classes are reference types, the class variables $(C pen) and $(C otherPen) provide access to the same $(C Pen) object. As a result, using either of those class variables affects the same object:
)
$(SHELL_SMALL
Before: 15 15
After : 12 12
)
$(P
That single object and the two class variables would be laid out in memory similar to the following figure:
)
$(MONO
(The Pen object) pen otherPen
───┬───────────────────┬─── ───┬───┬─── ───┬───┬───
│ ink │ │ o │ │ o │
───┴───────────────────┴─── ───┴─│─┴─── ───┴─│─┴───
▲ │ │
│ │ │
└────────────────────┴────────────┘
)
$(P
References $(I point at) actual variables as $(C pen) and $(C otherPen) do above.
)
$(P
Programming languages implement the reference and pointer concepts by special registers of the microprocessor, which are specifically for $(I pointing at) memory locations.
)
$(P
Behind the scenes, D's higher-level concepts (class variables, slices, associative arrays, etc.) are all implemented by pointers. As these higher-level features are already efficient and convenient, pointers are rarely needed in D programming. Still, it is important for D programmers to understand pointers well.
)
$(H5 $(IX *, pointer definition) Syntax)
$(P
The pointer syntax of D is mostly the same as in C. Although this can be seen as an advantage, the peculiarities of C's pointer syntax are necessarily inherited by D as well. For example, the different meanings of the $(C *) character may be confusing.
)
$(P
With the exception of $(C void) pointers, every pointer is associated with a certain type and can point at only variables of that specific type. For example, an $(C int) pointer can only point at variables of type $(C int).
)
$(P
The pointer definition syntax consists of the associated type and a $(C *) character:
)
---
$(I $(D_KEYWORD type_to_point_at)) * $(I name_of_the_pointer_variable);
---
$(P
Accordingly, a pointer variable that would be pointing at $(C int) variables would be defined like this:
)
---
int * myPointer;
---
$(P
The $(C *) character in that syntax may be pronounced as "pointer". So, the type of $(C myPointer) above is an "int pointer". The spaces before and after the $(C *) character are optional. The following syntaxes are common as well:
)
---
int* myPointer;
int *myPointer;
---
$(P
When it is specifically a pointer type that is being mentioned as in "int pointer", it is common to write the type without any spaces as in $(C int*).
)
$(H5 $(IX &, address of) Pointer value and the address-of operator $(C &))
$(P
Being variables themselves pointers have values as well. The default value of a pointer is the special value $(C null), which means that the pointer is not $(I pointing at) any variable yet (i.e. does not provide access to any variable).
)
$(P
To make a pointer provide access to a variable, the value of the pointer must be set to the address of that variable. The pointer starts pointing at the variable that is at that specific address. From now on, I will call that variable $(I the pointee).
)
$(P
The $(C &) operator which we have used many times before with $(C readf) has also been briefly mentioned in $(LINK2 /ders/d.en/value_vs_reference.html, the Value Types and Reference Types chapter). This operator produces the address of the variable that is written after it. Its value can be used when initializing a pointer:
)
---
int myVariable = 180;
int * myPointer = $(HILITE &)myVariable;
---
$(P
Initializing $(C myPointer) by the address of $(C myVariable) makes $(C myPointer) point at $(C myVariable).
)
$(P
The value of the pointer is the same as the address of $(C myVariable):
)
---
writeln("The address of myVariable: ", &myVariable);
writeln("The value of myPointer : ", myPointer);
---
$(SHELL_SMALL
The address of myVariable: 7FFF2CE73F10
The value of myPointer : 7FFF2CE73F10
)
$(P $(I $(B Note:) The address value is likely to be different every time the program is started.)
)
$(P
The following figure is a representation of these two variables in memory:
)
$(MONO
myVariable at myPointer at
address 7FFF2CE73F10 some other address
───┬────────────────┬─── ───┬────────────────┬───
│ 180 │ │ 7FFF2CE73F10 │
───┴────────────────┴─── ───┴────────│───────┴───
▲ │
│ │
└─────────────────────────────┘
)
$(P
The value of $(C myPointer) is the address of $(C myVariable), conceptually $(I pointing at) the variable that is at that location.
)
$(P
Since pointers are variables as well, the $(C &) operator can produce the address of the pointer as well:
)
---
writeln("The address of myPointer : ", &myPointer);
---
$(SHELL_SMALL
The address of myPointer : 7FFF2CE73F18
)
$(P
Since the difference between the two addresses above is 8, remembering that an $(C int) takes up 4 bytes, we can deduce that $(C myVariable) and $(C myPointer) are 4 bytes apart in memory.
)
$(P
After removing the arrow that represented the concept of $(I pointing at), we can picture the contents of memory around these addresses like this:
)
$(MONO
7FFF2CE73F10 7FFF2CE73F14 7FFF2CE73F18
: : : :
───┬────────────────┬────────────────┬────────────────┬───
│ 180 │ (unused) │ 7FFF2CE73F10 │
───┴────────────────┴────────────────┴────────────────┴───
)
$(P
The names of variables, functions, classes, etc. and keywords are not parts of programs of compiled languages like D. The variables that have been defined by the programmer in the source code are converted to bytes that occupy memory or registers of the microprocessor.
)
$(P
$(I $(B Note:) The names (a.k.a. symbols) may actually be included in programs to help with debugging but those names do not affect the operation of the program.)
)
$(H5 $(IX *, pointee access) The access operator $(C *))
$(P
We have seen above that the $(C *) character which normally represents multiplication is also used when defining pointers. A difficulty with the syntax of pointers is that the same character has a third meaning: It is also used when accessing the pointee through the pointer.
)
$(P
When it is written before the name of a pointer, it means $(I the variable that the pointer is pointing at) (i.e. the pointee):
)
---
writeln("The value that it is pointing at: ", $(HILITE *)myPointer);
---
$(SHELL_SMALL
The value that it is pointing at: 180
)
$(H5 $(IX ., pointer) The $(C .) (dot) operator to access a member of the pointee)
$(P
If you know pointers from C, this operator is the same as the $(C ->) operator in that language.
)
$(P
We have seen above that the $(C *) operator is used for accessing the pointee. That is sufficiently useful for pointers of fundamental types like $(C int*): The value of a fundamental type is accessed simply by writing $(C *myPointer).
)
$(P
However, when the pointee is a struct or a class object, the same syntax becomes inconvenient. To see why, let's consider the following struct:
)
---
struct Coordinate {
int x;
int y;
string toString() const {
return format("(%s,%s)", x, y);
}
}
---
$(P
The following code defines an object and a pointer of that type:
)
---
auto center = Coordinate(0, 0);
Coordinate * ptr = $(HILITE &)center; // pointer definition
writeln($(HILITE *)ptr); // object access
---
$(P
That syntax is convenient when accessing the value of the entire $(C Coordinate) object:
)
$(SHELL_SMALL
(0,0)
)
$(P
However, the code becomes complicated when accessing a member of an object through a pointer and the $(C *) operator:
)
---
// Adjust the x coordinate
(*ptr).x += 10;
---
$(P
That expression modifies the value of the $(C x) member of the $(C center) object. The left-hand side of that expression can be explained by the following steps:
)
$(UL
$(LI $(C ptr): The pointer that points at $(C center))
$(LI $(C $(HILITE *)ptr): Accessing the object (i.e. $(C center) itself))
$(LI $(C $(HILITE ()*ptr$(HILITE ))): Parentheses so that the $(C .) (dot) operator is applied to the object, not to the pointer)
$(LI $(C (*ptr)$(HILITE .x)): The $(C x) member of the object that $(C ptr) is pointing at)
)
$(P
To reduce the complexity of pointer syntax in D, the $(C .) (dot) operator is transferred to the pointee and provides access to the member of the object. (The exceptions to this rule are at the end of this section.)
)
$(P
So, the previous expression is normally written as:
)
---
$(HILITE ptr.x) += 10;
---
$(P
Since the pointer itself does not have a member named $(C x), $(C .x) is applied to the pointee and the $(C x) member of $(C center) gets modified:
)
$(SHELL_SMALL
(10,0)
)
$(P
Note that this is the same as the use of the $(C .) (dot) operator with classes. When the $(C .) (dot) operator is applied to a class $(I variable), it provides access to a member of the class $(I object):
)
---
class ClassType {
int member;
}
// ...
// Variable on the left, object on the right
ClassType variable = new ClassType;
// Applied to the variable but accesses the member of
// the object
variable.member = 42;
---
$(P
As you remember from $(LINK2 /ders/d.en/class.html, the Classes chapter), the class object is constructed by the $(C new) keyword on the right-hand side. $(C variable) is a class variable that provides access to it.
)
$(P
Realizing that it is the same with pointers is an indication that class variables and pointers are implemented similarly by the compiler.
)
$(P
There is an exception to this rule both for class variables and for pointers. Type properties like $(C .sizeof) are applied to the type of the pointer, not to the type of the pointee:
)
---
char c;
char * p;
writeln(p.sizeof); // size of the pointer, not the pointee
---
$(P
$(C .sizeof) produces the size of $(C p), which is a $(C char*), not the size of $(C c), which is a $(C char). On a 64-bit system pointers are 8-byte long:
)
$(SHELL_SMALL
8
)
$(H5 $(IX arithmetic, pointer) Modifying the value of a pointer)
$(P
The values of pointers can be incremented or decremented and they can be used in addition and subtraction:
)
---
++ptr;
--ptr;
ptr += 2;
ptr -= 2;
writeln(ptr + 3);
writeln(ptr - 3);
---
$(P
Different from their arithmetic counterparts, these operations do not modify the actual value by the specified amount. Rather, the value of the pointer gets modified so that it now points at the variable that is a certain number of variables beyond the current one. The amount of the increment or the decrement specifies $(I how many variables away) should the pointer now point at.
)
$(P
For example, incrementing the value of a pointer makes it point at the next variable:
)
---
++ptr; // Starts pointing at a variable that is next in
// memory from the old variable
---
$(P
For that to work correctly, the actual value of the pointer must be incremented by the size of the variable. For example, because the size of $(C int) is 4, incrementing a pointer of type $(C int*) changes its value by 4. The programmer need not pay attention to this detail; the pointer value is modified by the correct amount automatically.
)
$(P $(B Warning): It is undefined behavior to point at a location that is not a valid byte that belongs to the program. Even if it is not actually used to access any variable there, it is invalid for a pointer to point at a nonexistent variable. (The only exception of this rule is that it is valid to point at the imaginary element one past the end of an array. This will be explained later below.)
)
$(P
For example, it is invalid to increment a pointer that points at $(C myVariable), because $(C myVariable) is defined as a single $(C int):
)
---
++myPointer; $(CODE_NOTE_WRONG undefined behavior)
---
$(P
Undefined behavior means that it cannot be known what the behavior of the program will be after that operation. There may be systems where the program crashes after incrementing that pointer. However, on most modern systems the pointer is likely to point at the unused memory location that has been shown as being between $(C myVariable) and $(C myPointer) in the previous figure.
)
$(P
For that reason, the value of a pointer must be incremented or decremented only if there is a valid object at the new location. Arrays (and slices) have that property: The elements of an array are side by side in memory.
)
$(P
A pointer that is pointing at an element of a slice can be incremented safely as long as it does not go beyond the end of the slice. Incrementing such a pointer by the $(C ++) operator makes it point at the next element:
)
---
import std.stdio;
import std.string;
import std.conv;
enum Color { red, yellow, blue }
struct Crayon {
Color color;
double length;
string toString() const {
return format("%scm %s crayon", length, color);
}
}
void main() {
writefln("Crayon objects are %s bytes each.", Crayon.sizeof);
Crayon[] crayons = [ Crayon(Color.red, 11),
Crayon(Color.yellow, 12),
Crayon(Color.blue, 13) ];
$(HILITE Crayon * ptr) = $(HILITE &)crayons[0]; // (1)
for (int i = 0; i != crayons.length; ++i) {
writeln("Pointer value: ", $(HILITE ptr)); // (2)
writeln("Crayon: ", $(HILITE *ptr)); // (3)
$(HILITE ++ptr); // (4)
}
}
---
$(OL
$(LI Definition: The pointer is initialized by the address of the first element.)
$(LI Using its value: The value of the pointer is the address of the element that it is pointing at.)
$(LI Accessing the element that is being pointed at.)
$(LI Pointing at the next element.)
)
$(P
The output:
)
$(SHELL
Crayon objects are 16 bytes each.
Pointer value: 7F37AC9E6FC0
Crayon: 11cm red crayon
Pointer value: 7F37AC9E6FD0
Crayon: 12cm yellow crayon
Pointer value: 7F37AC9E6FE0
Crayon: 13cm blue crayon
)
$(P
Note that the loop above is iterated a total of $(C crayons.length) times so that the pointer is always used for accessing a valid element.
)
$(H5 Pointers are risky)
$(P
The compiler and the D runtime environment cannot guarantee that the pointers are always used correctly. It is the programmer's responsibility to ensure that a pointer is either $(C null) or points at a valid memory location (at a variable, at an element of an array, etc.).
)
$(P
For that reason, it is always better to consider higher-level features of D before thinking about using pointers.
)
$(H5 $(IX element one past the end) The element one past the end of an array)
$(P
It is valid to point at the imaginary element one past the end of an array.
)
$(P
This is a useful idiom that is similar to number ranges. When defining a slice with a number range, the second index is one past the elements of the slice:
)
---
int[] values = [ 0, 1, 2, 3 ];
writeln(values[1 .. 3]); // 1 and 2 included, 3 excluded
---
$(P
This idiom can be used with pointers as well. It is a common function design in C and C++ where a function parameter points at the first element and another one points at the element after the last element:
)
---
import std.stdio;
void tenTimes(int * begin, int * end) {
while (begin != end) {
*begin *= 10;
++begin;
}
}
void main() {
int[] values = [ 0, 1, 2, 3 ];
// The address of the second element:
int * begin = &values[1];
// The address of two elements beyond that one
tenTimes(begin, begin + 2);
writeln(values);
}
---
$(P
The value $(C begin + 2) means two elements after the one that $(C begin) is pointing at (i.e. the element at index 3).
)
$(P
The $(C tenTimes()) function takes two pointer parameters. It uses the element that the first one is pointing at but it never accesses the element that the second one is pointing at. As a result, only the elements at indexes 1 and 2 get modified:
)
$(SHELL_SMALL
[0, 10, 20, 3]
)
$(P
Such functions can be implemented by a $(C for) loop as well:
)
---
for ( ; begin != end; ++begin) {
*begin *= 10;
}
---
$(P
Two pointers that define a range can also be used with $(C foreach) loops:
)
---
foreach (ptr; begin .. end) {
*ptr *= 10;
}
---
$(P
For these methods to be applicable to $(I all of the elements) of a slice, the second pointer must necessarily point after the last element:
)
---
// The second pointer is pointing at the imaginary element
// past the end of the array:
tenTimes(begin, begin + values.length);
---
$(P
That is the reason why it is legal to point at the imaginary element one beyond the last element of an array.
)
$(H5 $(IX []) Using pointers with the array indexing operator $(C []))
$(P
Although it is not absolutely necessary in D, pointers can directly be used for accessing the elements of an array by an index value:
)
---
double[] floats = [ 0.0, 1.1, 2.2, 3.3, 4.4 ];
double * ptr = &floats[2];
*ptr = -100; // direct access to what it points at
ptr$(HILITE [1]) = -200; // access by indexing
writeln(floats);
---
$(P
The output:
)
$(SHELL_SMALL
[0, 1.1, -100, -200, 4.4]
)
$(P
In that syntax, the element that the pointer is pointing at is thought of being the first element of an imaginary slice. The $(C []) operator provides access to the specified element of that slice. The $(C ptr) above initially points at the element at index 2 of the original $(C floats) slice. $(C ptr[1]) is a reference to the element 1 of the imaginary slice that starts at $(C ptr) (i.e. index 3 of the original slice).
)
$(P
Although this behavior may seem complicated, there is a very simple conversion behind that syntax. Behind the scenes, the compiler converts the $(C pointer[index]) syntax to the $(C *(pointer + index)) expression:
)
---
ptr[1] = -200; // slice syntax
*(ptr + 1) = -200; // the equivalent of the previous line
---
$(P
As I have mentioned earlier, the compiler may not guarantee that this expression refers to a valid element. D's slices provide a much safer alternative and should be considered instead:
)
---
double[] slice = floats[2 .. 4];
slice[0] = -100;
slice[1] = -200;
---
$(P
Normally, index values are checked for slices at run time:
)
---
slice[2] = -300; // Runtime error: accessing outside of the slice
---
$(P
Because the slice above does not have an element at index 2, an exception would be thrown at run time (unless the program has been compiled with the $(C -release) compiler switch):
)
$(SHELL_SMALL
core.exception.RangeError@deneme(8391): Range violation
)
$(H5 $(IX slice from pointer) Producing a slice from a pointer)
$(P
Pointers are not as safe or as useful as slices because although they can be used with the slice indexing operator, they are not aware of the valid range of elements.
)
$(P
However, when the number of valid elements is known, a pointer can be used to construct a slice.
)
$(P
Let's assume that the $(C makeObjects()) function below is inside a C library. Let's assume that $(C makeObjects) makes specified number of $(C Struct) objects and returns a pointer to the first one of those objects:
)
---
Struct * ptr = makeObjects(10);
---
$(P
The syntax that produces a slice from a pointer is the following:
)
---
/* ... */ slice = pointer[0 .. count];
---
$(P
Accordingly, a slice to the 10 objects that are returned by $(C makeObjects()) can be constructed by the following code:
)
---
Struct[] slice = ptr[0 .. 10];
---
$(P
After that definition, $(C slice) is ready to be used safely in the program just like any other slice:
)
---
writeln(slice[1]); // prints the second element
---
$(H5 $(IX void*) $(C void*) can point at any type)
$(P
Although it is almost never needed in D, C's special pointer type $(C void*) is available in D as well. $(C void*) can point at any type:
)
---
int number = 42;
double otherNumber = 1.25;
void * canPointAtAnything;
canPointAtAnything = &number;
canPointAtAnything = &otherNumber;
---
$(P
The $(C void*) above is able to point at variables of two different types: $(C int) and $(C double).
)
$(P
$(C void*) pointers are limited in functionality. As a consequence of their flexibility, they cannot provide access to the pointee. When the actual type is unknown, its size is not known either:
)
---
*canPointAtAnything = 43; $(DERLEME_HATASI)
---
$(P
Instead, its value must first be converted to a pointer of the correct type:
)
---
int number = 42; // (1)
void * canPointAtAnything = &number; // (2)
// ...
int * intPointer = cast(int*)canPointAtAnything; // (3)
*intPointer = 43; // (4)
---
$(OL
$(LI The actual variable)
$(LI Storing the address of the variable in a $(C void*))
$(LI Assigning that address to a pointer of the correct type)
$(LI Modifying the variable through the new pointer)
)
$(P
It is possible to increment or decrement values of $(C void*) pointers, in which case their values are modified as if they are pointers of 1-byte types like $(C ubyte):
)
---
++canPointAtAnything; // incremented by 1
---
$(P
$(C void*) is sometimes needed when interacting with libraries that are written in C. Since C does not have higher level features like interfaces, classes, templates, etc. C libraries must rely on the $(C void*) type.
)
$(H5 Using pointers in logical expressions)
$(P
Pointers can automatically be converted to $(C bool). Pointers that have the value $(C null) produce $(C false) and the others produce $(C true). In other words, pointers that do not point at any variable are $(C false).
)
$(P
Let's consider a function that prints objects to the standard output. Let's design this function so that it also provides the number of bytes that it has just output. However, let's have it produce this information only when specifically requested.
)
$(P
It is possible to make this behavior optional by checking whether the value of a pointer is $(C null) or not:
)
---
void print(Crayon crayon, size_t * numberOfBytes) {
immutable info = format("Crayon: %s", crayon);
writeln(info);
$(HILITE if (numberOfBytes)) {
*numberOfBytes = info.length;
}
}
---
$(P
When the caller does not need this special information, they can pass $(C null) as the argument:
)
---
print(Crayon(Color.yellow, 7), $(HILITE null));
---
$(P
When the number of bytes is indeed important, then a non-$(C null) pointer value must be passed:
)
---
size_t numberOfBytes;
print(Crayon(Color.blue, 8), $(HILITE &numberOfBytes));
writefln("%s bytes written to the output", numberOfBytes);
---
$(P
Note that this is just an example. Otherwise, it would be better for a function like $(C print()) to return the number of bytes unconditionally:
)
---
size_t print(Crayon crayon) {
immutable info = format("Crayon: %s", crayon);
writeln(info);
return info.length;
}
---
$(H5 $(IX new) $(C new) returns a pointer for some types)
$(P
$(C new), which we have been using only for constructing class objects can be used with other types as well: structs, arrays, and fundamental types. The variables that are constructed by $(C new) are called dynamic variables.
)
$(P
$(C new) first allocates space from the memory for the variable and then constructs the variable in that space. The variable itself does not have a symbolic name in the compiled program; it would be accessed through the reference that is returned by $(C new).
)
$(P
The reference that $(C new) returns is a different kind depending on the type of the variable:
)
$(UL
$(LI For class objects, it is a $(I class variable):
---
Class classVariable = new Class;
---
)
$(LI For struct objects and variables of fundamental types, it is a $(I pointer):
---
Struct $(HILITE *) structPointer = new Struct;
int $(HILITE *) intPointer = new int;
---
)
$(LI For arrays, it is a $(I slice):
---
int[] slice = new int[100];
---
)
)
$(P
This distinction is usually not obvious when the type is not spelled-out on the left-hand side:
)
---
auto classVariable = new Class;
auto structPointer = new Struct;
auto intPointer = new int;
auto slice = new int[100];
---
$(P
The following program prints the return type of $(C new) for different kinds of variables:
)
---
import std.stdio;
struct Struct {
}
class Class {
}
void main() {
writeln(typeof(new int ).stringof);
writeln(typeof(new int[5]).stringof);
writeln(typeof(new Struct).stringof);
writeln(typeof(new Class ).stringof);
}