In traditional languages such as ANSI C, type visibility is determined by whether a type is nested inside of a function or other declaration. Types declared at the outer scope of a C program are associated with a single global namespace and are visible throughout the entire program. Types that are defined in C header files are typically included in this outer scope. Unlike these languages, D provides access to types from multiple outer scopes.
D is a language that facilitates dynamic observability across
multiple layers of a software stack, including the operating
system kernel, an associated set of loadable kernel modules, and
user processes that are running on the system. A single D
program can instantiate probes to gather data from multiple
kernel modules or other software entities that are compiled into
independent binary objects. Therefore, more than one data type
of the same name, perhaps with different definitions, might be
present in the universe of types that are available to DTrace
and the D compiler. To manage this situation, the D compiler
associates each type with a namespace, which is identified by
the containing program object. Types from a particular program
object can be accessed by specifying the object name and the
back quote (`
) scoping operator in any type
name.
For example, for a kernel module named foo
that contains the following C type declaration:
typedef struct bar { int x; } bar_t;
The types struct bar
and
bar_t
could be accessed from D using the
following type names:
struct foo`bar foo`bar_t
The back quote operator can be used in any context where a type name is appropriate, including when specifying the type for D variable declarations or cast expressions in D probe clauses.
The D compiler also provides two special, built-in type
namespaces that use the names C and D, respectively. The C type
namespace is initially populated with the standard ANSI C
intrinsic types, such as int
. In addition,
type definitions that are acquired by using the C preprocessor
(cpp), by running the dtrace
-C command, are processed by and added to the C scope.
As a result, you can include C header files containing type
declarations that are already visible in another type namespace
without causing a compilation error.
The D type namespace is initially populated with the D type
intrinsics, such as int
and
string
, as well as the built-in D type
aliases, such as uint64_t
. Any new type
declarations that appear in the D program source are
automatically added to the D type namespace. If you create a
complex type such as a struct
in a D program
consisting of member types from other namespaces, the member
types are copied into the D namespace by the declaration.
When the D compiler encounters a type declaration that does not specify an explicit namespace using the back quote operator, the compiler searches the set of active type namespaces to find a match by using the specified type name. The C namespace is always searched first, followed by the D namespace. If the type name is not found in either the C or D namespace, the type namespaces of the active kernel modules are searched in load address order, which does not guarantee any ordering properties among the loadable modules. To avoid type name conflicts with other kernel modules, you should use the scoping operator when accessing types that are defined in loadable kernel modules.
The D compiler uses the compressed ANSI C debugging information that is provided with the core Linux kernel modules to automatically access the types that are associated with the operating system source code, without the need to access the corresponding C include files. Note that this symbolic debugging information might not be available for all kernel modules on your system. The D compiler reports an error if you attempt to access a type within the namespace of a module that lacks the compressed C debugging information that is intended for use with DTrace.