Previous Up Next

3  Pointers

As in C, one should think of Cyclone pointers as just addresses. Operations on pointers, such as *x, x->f, and x[e], behave the same as in C, with the exception that run-time checks sometimes precede memory accesses. (Exactly when and where these checks occur is described below.) However, Cyclone prevents memory errors such as dereferencing dangling pointers or indexing outside an array's bounds, so it may reject some operations on pointers that C would accept.

In order to enforce memory safety and distinguish between different uses of pointers, Cyclone pointer types include additional qualifiers when compared to their C counterparts. These qualifiers are described briefly below and in more detail throughout this section:

3.1  Pointer Subtyping

Some pointer types may be safely used in contexts where another pointer type is expected. In particular, T*@notnull is a subtype of T*@nullable which means that a not-null pointer can be passed anywhere a possibly-null pointer is expected.

Similarly, a T*@numelts(42) pointer can be passed anywhere a T*@numelts(30) pointer is expected, because the former describes sequences that have at least 42 elements, which satisifes the constraint that it has at least 30 elements.

In addition, T*@region(`r) is a subtype of T*@region(`s) when region `r outlives region `s. The heap region (`H) outlives every region so you can safely use a heap pointer anywhere another region is expected. Outer blocks and outer regions outlive inner blocks and regions. For example the following code is type-correct:
  void foo(int x) {
    int *@region(`foo) y = &x;
      int *@region(`L) z = y;
because region `foo outlives region `L. By default, regions passed in to a function outlive any regions defined in the function (because they will live across the function call). Finally, you can specify outlives relations among region parameters within a function's prototype. The following code specifies that input region `r outlives input region `s so it's safe to treat `r pointers as if they were `s pointers:
  void bar(int *@region(`r) x,
           int *@region(`s) y : {`s} > `r);
In general, the outlives relation is specified after the function arguments by separating the relations with a colon (:) and giving a comma-separated list of primitive outlives relations. These outlives relations are of the form ``{`r1,...,`rn} > `r'' and specify that region `r outlives all of the regions `r1 through `rn.

Finally, when T is a subtype of S, then T* is a subtype of const S*. So, for instance, if we declare:
// nullable int pointers
typedef int * nintptr_t;
// not-nullable int pointers
typedef int *@notnull intptr_t;
then intptr_t * is a subtype of const nintptr_t *. Note, however, that ``const'' is important to get this kind of deep subtyping.

The following example shows what could go wrong if we allowed deep subtyping without the const:
  void f(int *@notnull *@notnull x) {
    int *@nullable *@notnull y = x; 
    // would be legal if int *@nullable *@notnull 
    // was a subtype of int *@notnull *@notnull.
    *y = NULL;    
    // legal because *y has type int *@nullable
    // seg faults even though the type of *x is 
    // int *@notnull

3.2  Pointer Coercions

In addition to pointer subtyping, Cyclone provides a number of coercions which allow you to convert a pointer value from one type to another. For instance, you can coerce a thin pointer with 42 elements to a fat pointer:
  int arr[42];
  int *@thin @numelts(42) p = arr;
  int *@fat pfat = p;
As another example, you can coerce a thin, zero-terminated pointer to a fat, zero-terminated pointer:
  int strlen(char *@zeroterm s) {
    char *@fat @zeroterm sfat = s;
    return numelts(s);
In both cases, the compiler inserts code to convert from the thin representation to an appropriate fat representation. In the former case, the bounds information can be calculated statically. In the latter case, the bounds information is calculated dynamically (by looking for the zero that terminates the sequence.) In both cases, the coercion is guaranteed to succeed, so the compiler does not emit a warning.

In other cases, a coercion can cause a run-time exception to be thrown. For instance, if you attempt to coerce a @nullable pointer to a @notnull pointer, and the value happens to be NULL, then the exception Null_Exception is thrown. In general, the compiler will warn you when you try to coerce from one pointer representation to another where a run-time check must be inserted, and that check might fail. A dataflow analysis is used to avoid some warnings, but in general, it's not smart enough to get rid of all of them. In these cases, you can explicitly cast the pointer from one representation to the other, and the compiler will not generate a warning (though it will still insert the run-time check to ensure safety.)

Here is a list of some of the coercions that are possible:

3.3  Default Region Qualifiers

The rules the compiler uses for filling in @region qualifiers when they are omitted from pointer types are a little complicated, but they are designed to avoid clutter in the common case: Thus, be warned that
typedef int * foo_t;
void g(foo_t);
is different than
void g(int *);
The reason is clear when we fill in the default region qualifiers. In the first case, we have:
typedef int *@region(`H) foo_t;
void g(foo_t);
whereas in the second case we have:
void g(int *@region(`r));

3.4  Static Expression Bounds

The bound for the @numelts qualifier must be a static expression. A static expression is either a constant expression, or an expression involving valueof(T) for a type-level expression T. The valueof construct is used to connect the value of a run-time integer to the static bound on an array. For example, the following function takes in an integer num and pointer to a sequence of num integers and returns the sum of the sequence:
  int sum(tag_t<`n> num, 
          int *@notnull @numelts(valueof(`n)) p) {
    int a = 0;
    for (unsigned i = 0; i < num; i++) 
      a += p[i];
The type of num is specified as tag_t<`n>. This simply means that num holds an integer value, called `n, and the number of elements of p is equal to n. This form of dependency is common enough that it can be abbreviated as follows:
  int sum(tag_t num, int p[num]);
and the compiler will fill in the missing information.

Previous Up Next