C pointers
last modified November 14, 2026
A pointer is a variable that holds the address of another object. Pointers let a
function change the caller's variables, give access to arrays and strings, refer
to memory allocated at run time, and link structures together into lists and
trees. They are also where most serious C bugs come from. This tutorial covers
the address-of and dereference operators, passing pointers to functions,
NULL, pointer arithmetic, the relation between arrays and pointers,
const pointers, pointers to pointers, void pointers,
pointers to structures, the lifetime of the object pointed to, and the errors
that tools can detect.
Address-of and dereference
The unary & operator gives the address of an object, and the
unary * operator, applied to a pointer, gives the object at that
address. A pointer is declared with an asterisk before its name, and its type
records the type of the object it points to.
#include <stdio.h>
int main(void) {
int n = 25;
int *p = &n;
printf("value of n: %d\n", n);
printf("address of n: %p\n", (void *) &n);
printf("value of p: %p\n", (void *) p);
printf("value at *p: %d\n", *p);
printf("sizeof p: %zu\n", sizeof p);
return 0;
}
p is a pointer to int initialized with the address of
n, so *p is n itself. The
%p conversion expects a void * argument, hence the
casts. The addresses are different on every run, because Linux places the stack
at a random position for security (address space layout randomization). A
pointer occupies 8 bytes on a 64-bit system regardless of the type it points to.
$ gcc -std=c17 -Wall -Wextra -o address address.c $ ./address value of n: 25 address of n: 0x7ffe030e0a74 value of p: 0x7ffe030e0a74 value at *p: 25 sizeof p: 8
Changing a value through a pointer
A dereferenced pointer can stand on the left side of an assignment like any
variable. Every change made through *p is a change of the object
p points to.
#include <stdio.h>
int main(void) {
int count = 10;
int *p = &count;
*p = 20;
printf("count = %d\n", count);
(*p)++;
printf("count = %d\n", count);
*p *= 2;
printf("count = %d\n", count);
return 0;
}
The three statements modify count without naming it. The
parentheses in (*p)++ are required: postfix ++ binds
tighter than *, so *p++ would increment the pointer
and leave the value alone. Compound assignment such as *p *= 2
works as expected.
$ gcc -std=c17 -Wall -Wextra -o modify modify.c $ ./modify count = 20 count = 21 count = 42
Passing pointers to functions
C passes arguments by value: a function receives copies of the arguments and cannot change the caller's variables through them. Passing the addresses of the variables instead lets the function reach the originals.
#include <stdio.h>
static void swap_values(int a, int b) {
int tmp = a;
a = b;
b = tmp;
}
static void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
int main(void) {
int x = 1, y = 2;
swap_values(x, y);
printf("after swap_values: x = %d, y = %d\n", x, y);
swap(&x, &y);
printf("after swap: x = %d, y = %d\n", x, y);
return 0;
}
swap_values exchanges its own copies, and the exchange is lost when
it returns. swap receives two addresses and exchanges the values
stored at them. The pointers themselves are still passed by value, but a copy of
an address points to the same object as the original.
$ gcc -std=c17 -Wall -Wextra -o swap swap.c $ ./swap after swap_values: x = 1, y = 2 after swap: x = 2, y = 1
Returning several values
A function returns a single value, but it can store further results through
pointer parameters, often called output parameters. Many standard functions,
such as strtol and scanf, work this way.
#include <stdio.h>
static void min_max(const int *vals, size_t n, int *min, int *max) {
*min = *max = vals[0];
for (size_t i = 1; i < n; i++) {
if (vals[i] < *min) *min = vals[i];
if (vals[i] > *max) *max = vals[i];
}
}
int main(void) {
int vals[] = { 4, -2, 17, 9, 0, 11 };
int lo, hi;
min_max(vals, sizeof vals / sizeof vals[0], &lo, &hi);
printf("min: %d, max: %d\n", lo, hi);
return 0;
}
min_max reads the array through a pointer to const
int, which documents that it does not change the elements, and writes the
two results through min and max. The caller passes the
addresses of lo and hi, which do not need to be
initialized because the function assigns them before reading them.
$ gcc -std=c17 -Wall -Wextra -o out_params out_params.c $ ./out_params min: -2, max: 17
The null pointer
NULL is a pointer value that points to no object. Functions return
it to signal that there is no result, and a pointer that does not yet refer to
anything is initialized with it. Dereferencing a null pointer is undefined
behavior, so a pointer that may be null is tested first.
#include <stdio.h>
#include <string.h>
static const char *find_word(const char *words[], size_t n, const char *key) {
for (size_t i = 0; i < n; i++) {
if (strcmp(words[i], key) == 0) {
return words[i];
}
}
return NULL;
}
int main(void) {
const char *words[] = { "red", "green", "blue" };
const char *keys[] = { "green", "purple" };
for (size_t i = 0; i < 2; i++) {
const char *res = find_word(words, 3, keys[i]);
if (res != NULL) {
printf("%s: found at %p\n", keys[i], (void *) res);
} else {
printf("%s: not found\n", keys[i]);
}
}
return 0;
}
find_word returns a pointer to the matching string or
NULL. The caller compares the result with NULL before
using it; if (res) is an equivalent and common shorthand. The
address printed for the found word is low because it points to a string literal
in the program's read-only data, not to the stack.
$ gcc -std=c17 -Wall -Wextra -o null_ptr null_ptr.c $ ./null_ptr green: found at 0x402008 purple: not found
Pointer arithmetic
Adding an integer to a pointer moves it by that many elements, not bytes. The
compiler multiplies the integer by the size of the pointed-to type, so the same
expression p + 1 advances by a different number of bytes for
different types.
#include <stdio.h>
int main(void) {
char c[3];
int i[3];
double d[3];
char *pc = c;
int *pi = i;
double *pd = d;
printf("char: %p %p\n", (void *) pc, (void *) (pc + 1));
printf("int: %p %p\n", (void *) pi, (void *) (pi + 1));
printf("double: %p %p\n", (void *) pd, (void *) (pd + 1));
printf("byte steps: %td %td %td\n",
(char *) (pc + 1) - (char *) pc,
(char *) (pi + 1) - (char *) pi,
(char *) (pd + 1) - (char *) pd);
return 0;
}
The addresses are printed in hexadecimal: pc + 1 is one byte past
pc, pi + 1 four bytes past pi and
pd + 1 eight bytes past pd. Converting both pointers
to char * before subtracting measures the distance in bytes, which
confirms the sizes of char, int and
double. Arithmetic is only defined within one array and up to one
element past its end.
$ gcc -std=c17 -Wall -Wextra -o arithmetic arithmetic.c $ ./arithmetic char: 0x7ffe146944c5 0x7ffe146944c6 int: 0x7ffe146944b8 0x7ffe146944bc double: 0x7ffe146944a0 0x7ffe146944a8 byte steps: 1 4 8
Walking an array with a pointer
Pointer arithmetic is the natural way to traverse an array: a pointer starts at the first element and is incremented until it reaches the end. The address one past the last element may be computed and compared, but not dereferenced.
#include <stdio.h>
int main(void) {
int vals[] = { 3, 8, 1, 9, 4 };
size_t n = sizeof vals / sizeof vals[0];
for (size_t i = 0; i < n; i++) {
printf("%d ", vals[i]);
}
printf("\n");
for (int *p = vals; p < vals + n; p++) {
printf("%d ", *p);
}
printf("\n");
int *end = vals + n;
int sum = 0;
for (int *p = vals; p != end; p++) {
sum += *p;
}
printf("sum: %d\n", sum);
return 0;
}
The first loop uses an index, the second the condition
p < vals + n, and the third an explicit end pointer. All three
visit the same elements. The end-pointer form appears often in C code and is the
model for iterator ranges in C++.
$ gcc -std=c17 -Wall -Wextra -o traverse traverse.c $ ./traverse 3 8 1 9 4 3 8 1 9 4 sum: 25
Subtracting and comparing pointers
Subtracting two pointers into the same array gives the number of elements
between them, as a value of type ptrdiff_t from
<stddef.h>. Pointers into the same array can also be
compared with the relational operators, and an index can be negative when the
pointer is not at the start of the array.
#include <stddef.h>
#include <stdio.h>
int main(void) {
int vals[] = { 10, 20, 30, 40, 50, 60 };
int *first = &vals[1];
int *last = &vals[4];
ptrdiff_t dist = last - first;
printf("elements between: %td\n", dist);
printf("last > first: %d\n", last > first);
printf("*(first + 2): %d\n", *(first + 2));
printf("first[2]: %d\n", first[2]);
printf("last[-1]: %d\n", last[-1]);
return 0;
}
first points to the element 20 and last to 50, so
they are three elements apart. first[2] is defined as
*(first + 2) and gives 40, and last[-1] steps one
element back to the same value. %td is the printf
conversion for ptrdiff_t.
$ gcc -std=c17 -Wall -Wextra -o difference difference.c $ ./difference elements between: 3 last > first: 1 *(first + 2): 40 first[2]: 40 last[-1]: 40
Arrays and pointers
An array is not a pointer, but in most expressions an array is converted to a
pointer to its first element. This conversion, called decay, does not happen
with sizeof and the address-of operator, and it always happens when
an array is passed to a function.
#include <stdio.h>
static void show(int arr[], size_t n) {
printf("in show: sizeof arr = %zu\n", sizeof arr);
for (size_t i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main(void) {
int vals[5] = { 1, 2, 3, 4, 5 };
int *p = vals;
printf("in main: sizeof vals = %zu\n", sizeof vals);
printf("in main: sizeof p = %zu\n", sizeof p);
printf("vals == &vals[0]: %d\n", vals == &vals[0]);
printf("vals[2] = %d, *(vals + 2) = %d, 2[vals] = %d\n",
vals[2], *(vals + 2), 2[vals]);
show(vals, sizeof vals / sizeof vals[0]);
return 0;
}
In main, sizeof vals is the size of the array, 20
bytes, while sizeof p is the size of a pointer. The subscript
a[i] is defined as *(a + i), which is why the odd
2[vals] also works. The parameter int arr[] is really
int *arr, so inside show the array size is lost; GCC
warns about the misleading sizeof, and the length has to be passed
as a separate argument.
$ gcc -std=c17 -Wall -Wextra -o decay decay.c
decay.c: In function ‘show’:
decay.c:5:50: warning: ‘sizeof’ on array function parameter ‘arr’ will return size of ‘int *’ [-Wsizeof-array-argument]
5 | printf("in show: sizeof arr = %zu\n", sizeof arr);
| ^~~
decay.c:3:22: note: declared here
3 | static void show(int arr[], size_t n) {
| ~~~~^~~~~
$ ./decay
in main: sizeof vals = 20
in main: sizeof p = 8
vals == &vals[0]: 1
vals[2] = 3, *(vals + 2) = 3, 2[vals] = 3
in show: sizeof arr = 8
1 2 3 4 5
Pointers and const
const can apply to the object a pointer points to, to the pointer
itself, or to both. Reading the declaration from right to left helps:
const int *p is a pointer to a constant integer, and
int *const p is a constant pointer to an integer.
#include <stdio.h>
static int sum(const int *vals, size_t n) {
int total = 0;
for (size_t i = 0; i < n; i++) {
total += vals[i];
}
return total;
}
int main(void) {
int a = 1, b = 2;
int vals[] = { 1, 2, 3 };
const int *pc = &a;
pc = &b;
int *const cp = &a;
*cp = 10;
const int *const cc = &b;
printf("*pc = %d, *cp = %d, *cc = %d\n", *pc, *cp, *cc);
printf("sum = %d\n", sum(vals, 3));
return 0;
}
pc can be redirected from a to b, but
*pc cannot be assigned. cp must stay pointed at
a, but the value can be changed through it, and cc
allows neither. The parameter of sum uses the first form, which is
the most common use of const with pointers: it promises the caller
that the function only reads the data.
$ gcc -std=c17 -Wall -Wextra -o const_ptr const_ptr.c $ ./const_ptr *pc = 2, *cp = 10, *cc = 2 sum = 6
Violating const
The compiler enforces the qualifiers. The next program tries the two forbidden
operations: writing through a pointer to const and redirecting a
const pointer.
#include <stdio.h>
int main(void) {
int a = 1, b = 2;
const int *pc = &a;
*pc = 5;
int *const cp = &a;
cp = &b;
printf("%d %d\n", *pc, *cp);
return 0;
}
Both assignments are rejected with an error, so no executable is produced. The
messages distinguish the two cases: *pc is a read-only location,
while cp is a read-only variable.
$ gcc -std=c17 -Wall -Wextra -o const_error const_error.c
const_error.c: In function ‘main’:
const_error.c:8:9: error: assignment of read-only location ‘*pc’
8 | *pc = 5;
| ^
const_error.c:11:8: error: assignment of read-only variable ‘cp’
11 | cp = &b;
| ^
Pointers to pointers
A pointer is an object too, so it has an address that can be stored in a pointer to a pointer. The main use is a function that must change a pointer owned by the caller, for example to hand back newly allocated memory.
#include <stdio.h>
#include <stdlib.h>
static int make_array(int **out, size_t n) {
int *arr = malloc(n * sizeof *arr);
if (arr == NULL) {
return -1;
}
for (size_t i = 0; i < n; i++) {
arr[i] = (int) (i * i);
}
*out = arr;
return 0;
}
int main(void) {
int x = 7;
int *p = &x;
int **pp = &p;
printf("x = %d, *p = %d, **pp = %d\n", x, *p, **pp);
int *squares = NULL;
if (make_array(&squares, 6) != 0) {
perror("malloc");
return 1;
}
for (size_t i = 0; i < 6; i++) {
printf("%d ", squares[i]);
}
printf("\n");
free(squares);
return 0;
}
pp holds the address of p, so *pp is
p and **pp is x. make_array
receives the address of the caller's squares and stores the
allocated block in it through *out; the return value is free to
report success or failure. Had the parameter been a plain int *,
the function would have changed only its own copy and leaked the memory.
$ gcc -std=c17 -Wall -Wextra -o ptr_to_ptr ptr_to_ptr.c $ ./ptr_to_ptr x = 7, *p = 7, **pp = 7 0 1 4 9 16 25
Arrays of pointers
The parameter argv of main is an array of pointers to
the command-line arguments, and it decays to a char **. The standard
guarantees that argv[argc] is a null pointer, so the array can also
be walked without the count.
#include <stdio.h>
int main(int argc, char *argv[]) {
for (char **p = argv; *p != NULL; p++) {
printf("%td: %s\n", p - argv, *p);
}
printf("argc = %d, argv[argc] is %s\n", argc,
argv[argc] == NULL ? "NULL" : "not NULL");
return 0;
}
The loop variable p points to the elements of argv in
turn, so *p is one argument string, and p - argv is
its index. The loop stops at the terminating null pointer. The first argument is
the name the program was started with.
$ gcc -std=c17 -Wall -Wextra -o argv_ptrs argv_ptrs.c $ ./argv_ptrs red green 0: ./argv_ptrs 1: red 2: green argc = 3, argv[argc] is NULL
Pointers to arrays
A two-dimensional array is an array of rows, and it decays to a pointer to its
first row. The type of that pointer, int (*)[4], includes the row
length, so incrementing it skips a whole row. The parentheses matter:
int *row[4] would be an array of four pointers.
#include <stdio.h>
static void print_matrix(int rows, int cols, int (*m)[cols]) {
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
printf("%3d", m[r][c]);
}
printf("\n");
}
}
int main(void) {
int grid[3][4] = {
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
int (*row)[4] = grid;
printf("sizeof *row: %zu\n", sizeof *row);
printf("row[1][2] = %d\n", row[1][2]);
printf("(*(row + 2))[0] = %d\n", (*(row + 2))[0]);
print_matrix(3, 4, grid);
return 0;
}
*row is a row of four integers, 16 bytes. row + 2
points to the third row, and (*(row + 2))[0] is its first element,
written more simply as row[2][0]. print_matrix takes
the column count first and uses it in the parameter type
int (*m)[cols], a pointer to a variable-length array, so one
function handles matrices of any width.
$ gcc -std=c17 -Wall -Wextra -o two_dim two_dim.c $ ./two_dim sizeof *row: 16 row[1][2] = 7 (*(row + 2))[0] = 9 1 2 3 4 5 6 7 8 9 10 11 12
void pointers
A void * can hold the address of an object of any type, and C
converts between void * and other object pointers without a cast.
It cannot be dereferenced or used in arithmetic, because the size of the target
is unknown; generic code passes the size separately and works on the bytes.
#include <stdio.h>
#include <string.h>
static void swap_any(void *a, void *b, size_t size) {
unsigned char tmp[64];
if (size > sizeof tmp) {
return;
}
memcpy(tmp, a, size);
memcpy(a, b, size);
memcpy(b, tmp, size);
}
static void dump_bytes(const void *data, size_t size) {
const unsigned char *p = data;
for (size_t i = 0; i < size; i++) {
printf("%02x ", p[i]);
}
printf("\n");
}
int main(void) {
int i1 = 1, i2 = 2;
double d1 = 1.5, d2 = -2.25;
swap_any(&i1, &i2, sizeof i1);
swap_any(&d1, &d2, sizeof d1);
printf("i1 = %d, i2 = %d\n", i1, i2);
printf("d1 = %.2f, d2 = %.2f\n", d1, d2);
unsigned int v = 0x11223344;
dump_bytes(&v, sizeof v);
return 0;
}
swap_any exchanges two objects of any type through a temporary
buffer of bytes, which is how qsort and similar library functions
handle arbitrary elements. dump_bytes converts its
void * to const unsigned char * to look at the bytes
one by one. The value 0x11223344 is stored with its lowest byte first, which
shows that x86-64 is a little-endian machine.
$ gcc -std=c17 -Wall -Wextra -o void_ptr void_ptr.c $ ./void_ptr i1 = 2, i2 = 1 d1 = -2.25, d2 = 1.50 44 33 22 11
Pointers to structures
Structures are usually passed to functions by pointer, which avoids copying them
and allows the function to modify them. The -> operator accesses
a member through a pointer: p->x means (*p).x.
#include <stdio.h>
struct point {
int x;
int y;
};
static void move(struct point *p, int dx, int dy) {
p->x += dx;
p->y += dy;
}
int main(void) {
struct point pts[] = { { 0, 0 }, { 3, 4 }, { -1, 2 } };
size_t n = sizeof pts / sizeof pts[0];
for (struct point *p = pts; p < pts + n; p++) {
move(p, 10, 10);
}
for (size_t i = 0; i < n; i++) {
printf("(%d, %d)\n", pts[i].x, (&pts[i])->y);
}
return 0;
}
The loop walks the array of structures with a pointer, exactly like an array of
integers, and passes each element's address to move. The output
loop shows that pts[i].x and (&pts[i])->y are
two ways to reach a member. Structures are the subject of a separate tutorial.
$ gcc -std=c17 -Wall -Wextra -o struct_ptr struct_ptr.c $ ./struct_ptr (10, 10) (13, 14) (9, 12)
Dangling pointers
A pointer is only valid while the object it points to exists. A local variable ceases to exist when its function returns, so returning its address produces a dangling pointer.
#include <stdio.h>
static int *make_counter(void) {
int counter = 0;
return &counter;
}
int main(void) {
int *c = make_counter();
printf("%p\n", (void *) c);
return 0;
}
GCC recognizes the mistake and warns. It also replaces the returned address
with a null pointer, as the output shows, so the bug turns into a predictable
crash at the first dereference instead of silent corruption; other compilers
and less obvious cases are not so forgiving. The same problem arises with a pointer to memory that has
been released with free.
$ gcc -std=c17 -Wall -Wextra -o dangling dangling.c
dangling.c: In function ‘make_counter’:
dangling.c:7:12: warning: function returns address of local variable [-Wreturn-local-addr]
7 | return &counter;
| ^~~~~~~~
$ ./dangling
(nil)
Returning valid pointers
There are three correct ways to give a caller a pointer that outlives the function: point to an object with static storage duration, allocate the object on the heap, or let the caller supply the memory.
#include <stdio.h>
#include <stdlib.h>
static int *next_id(void) {
static int id = 0;
id++;
return &id;
}
static int *new_value(int v) {
int *p = malloc(sizeof *p);
if (p != NULL) {
*p = v;
}
return p;
}
static void fill(int *buf, size_t n) {
for (size_t i = 0; i < n; i++) {
buf[i] = (int) i + 1;
}
}
int main(void) {
next_id();
int *id = next_id();
printf("static: %d\n", *id);
int *v = new_value(42);
if (v == NULL) {
return 1;
}
printf("heap: %d\n", *v);
free(v);
int buf[4];
fill(buf, 4);
printf("caller buffer: %d %d %d %d\n", buf[0], buf[1], buf[2], buf[3]);
return 0;
}
The static variable in next_id exists for the whole
run, so its address stays valid, although every call shares the same object.
new_value returns heap memory, which the caller must free. The
third form, where fill writes into a buffer owned by the caller, is
the most common in the standard library, as in fgets and
snprintf. Allocation is covered in
C malloc and free.
$ gcc -std=c17 -Wall -Wextra -o lifetime lifetime.c $ ./lifetime static: 2 heap: 42 caller buffer: 1 2 3 4
Uninitialized pointers
A local pointer that is not initialized holds an arbitrary value, and writing through it can corrupt any part of memory. Unlike a null pointer, such a pointer cannot be tested.
#include <stdio.h>
int main(void) {
int *p;
int *q = NULL;
if (q == NULL) {
printf("q is NULL, not dereferenced\n");
}
*p = 5;
printf("%d\n", *p);
return 0;
}
With -Wall, GCC warns that p is used uninitialized.
In this run the arbitrary address is not mapped and the program crashes, which
is the lucky outcome. Initializing every pointer, with a valid address or with
NULL, removes this whole class of bugs.
$ gcc -std=c17 -Wall -Wextra -o uninit uninit.c
uninit.c: In function ‘main’:
uninit.c:12:8: warning: ‘p’ is used uninitialized [-Wuninitialized]
12 | *p = 5;
| ~~~^~~
uninit.c:5:10: note: ‘p’ was declared here
5 | int *p;
| ^
$ ./uninit
q is NULL, not dereferenced
Segmentation fault (core dumped)
Out-of-bounds access
C does not check array indexes or pointer offsets. A loop that runs one step too
far reads whatever lies after the array. The undefined behavior sanitizer,
enabled with -fsanitize=undefined, adds checks for indexes into
arrays whose size is known.
#include <stdio.h>
int main(void) {
int vals[5] = { 1, 2, 3, 4, 5 };
int sum = 0;
for (int i = 0; i <= 5; i++) {
sum += vals[i];
}
printf("sum = %d\n", sum);
return 0;
}
The condition i <= 5 should be i < 5. The
sanitizer reports the index of vals[5] and then the load from an
address outside the array, both with the source line, and lets the program
continue; the sum includes whatever value happened to follow the array, here 0. Without the sanitizer the program prints a result with no warning at
all. AddressSanitizer, shown in C strings, catches
the same error for heap memory and through pointers.
$ gcc -std=c17 -Wall -Wextra -fsanitize=undefined -o bounds bounds.c
$ ./bounds
bounds.c:9:20: runtime error: index 5 out of bounds for type 'int [5]'
bounds.c:9:20: runtime error: load of address 0x7fff5ba4d454 with insufficient space for an object of type 'int'
0x7fff5ba4d454: note: pointer points here
05 00 00 00 00 00 00 00 05 00 00 00 0f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
^
sum = 15
Guidelines
Pointer bugs are hard to find because the program often keeps running with corrupted data. A few habits prevent most of them.
- Initialize every pointer, with a valid address or with
NULL. - Check pointers that may be null before dereferencing them.
- Pass the length together with every array, since the parameter is only a pointer.
- Use
constfor pointers through which nothing is written. - Never return the address of a local variable.
- Set a pointer to
NULLafter freeing it when it stays in scope. - Cast to
void *when printing a pointer with%p. - Build and test with
-fsanitize=address,undefined.
Source
cppreference: pointer declaration
cppreference: member access operators
cppreference: array declaration
GCC: instrumentation options
This tutorial covered pointers in C: the address-of and dereference operators,
passing pointers to functions, NULL, pointer arithmetic, the
relation between arrays and pointers, const pointers, pointers to
pointers and to arrays, void pointers, pointers to structures,
object lifetime and the errors sanitizers detect. Strings, which are accessed
almost entirely through pointers, are covered in
C strings, arrays in
C arrays, parameter passing in
C functions, and heap memory in
C malloc and free.
Author
List all C tutorials.