返回介绍

Arrays and pointers

发布于 2025-02-25 23:43:59 字数 8495 浏览 0 评论 0 收藏 0

Automatic arrays

If you know the size of the arrays at initialization (i.e. when the program is first run), you can usually get away with the use of fixed size arrays for which C will automatically manage memory for you.

int len = 3;

// Giving an explicit size
double xs[len];
for (int i=0; i<len; i++) {
    xs[i] = 0.0;
}

// C can infer size if initializer is given
double ys[] = {1, 2, 3};

Pointers and dynamic memory management

Otherwise, we have to manage memory ourselves using pointers. Bascially, memory in C can be auotmatic, static or dynamic. Variables in automatic memory are managed by the computer on the stack, when it goes out of scope, the varible disappears. Static variables essentially live forever. Dynamic memory is allocated in the heap, and you manage its lifetime.

Mini-glossary: * scope : Where a variable is visible - basically C variables have block scope - variables either live within a pair of curly braces (inlucdes variables in parentheses just before block such as function arguments and the counter in a for loop), or they are visible thorughout the file. * stack : Computer memory is divided into a stack (small) and a heap (big). Automatic varianbles are put on the stack; dynamcic variables are put in the heap. Hence if you have a very large array, you would use dynamic memory allocation even if you knwe its size at initialization.

Any variable in memory has an address represented as a 64-bit integer in most operating systems. A pointer is basically an integer containing the address of a block of memory. This is what is returned by functions such as malloc . In C, a pointer is dentoed by * . However, the * notation is confusing because its interpreation depends on whehter you are using it in a declaraiton or not. In a declaration

int *p = malloc(sizeof(int)); // p is a pointer to an integer
*p = 5; // *p is an integer

To get the actual address value, we can use the & address opertor. This is often used so that a function can alter the value of an argument passed in (e.g. see address.c below).

%%file pointers.c
#include <stdio.h>

int main()
{
    int i = 2;
    int j = 3;
    int *p;
    int *q;
    *p = i;
    q = &j;
    printf("p  = %p\n", p);
    printf("*p = %d\n", *p);
    printf("&p = %p\n", &p);
    printf("q  = %p\n", q);
    printf("*q = %d\n", *q);
    printf("&q = %p\n", &q);
}
%%bash

clang -Wall -Wno-uninitialized pointers.c -o pointers
./pointers

Passing by value and passing by reference

%%file by_val.c
#include <stdio.h>

void change_arg(int p) {
    p *= 2;
}

int main()
{
    int x = 5;
    change_arg(x);
    printf("%d\n", x);
}
%%bash

clang -Wall by_val.c -o by_val
./by_val
%%file by_ref.c
#include <stdio.h>

void change_arg(int *p) {
    *p *= 2;
}
int main()
{
    int x = 5;
    change_arg(&x);
    printf("%d\n", x);
}
%%bash

clang -Wall by_ref.c -o by_ref
./by_ref

Pointers to pointers to pointers - just remember that a pointer is simply a name for an integer that represents an address; since it is an integer, it also has an address ...

%%file ptr.c
#include <stdio.h>

int main() {
    int x = 2;
    int *p = &x;
    int **q = &p;
    int ***r = &q;

    printf("%d, %p, %p, %p, %p, %p, %p, %d", x, &x, p, &p, q, &q, r, ***r);
}
%%bash
gcc ptr.c -o ptr
./ptr

Pointer arithmetic

If we want to store a whole sequence of ints, we can do so by simply allocating more memory:

int *ps = malloc(5 * sizeof(int)); // ps is a pointer to an integer
for (int i=0; i<5; i++) {
    ps[i] = i;
}

The computer will find enough space in the heap to store 5 consecutive integers in a contiguour way. Since C arrays are all fo the same type, this allows us to do pointer arithmetic - i.e. the pointer ps is the same as &ps[0] and ps + 2 is the same as &ps[2] . An example at this point is helpful.

%%file pointers2.c
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int *ps = malloc(5 * sizeof(int));
    for (int i =0; i < 5; i++) {
        ps[i] = i + 10;
    }

    printf("%d, %d\n", *ps, ps[0]); // remmeber that *ptr is just a regular variable outside of a declaration, in this case, an int
    printf("%d, %d\n", *(ps+2), ps[2]);
    printf("%d, %d\n", *(ps+4), *(&ps[4])); // * and & are inverses

    free(ps); // avoid memory leak
}
%%bash

clang -Wall pointers2.c -o pointers2
./pointers2

Pointers and arrays

An array name is actualy just a constant pointer to the address of the beginning of the array. Hence, we can derferecne an array name just like a pointer. We can also do pointer arithmetic with array names - this leads to the following legal but weird syntax:

arr[i] = *(arr + i) = i[arr]
%%file array_pointer.c
#include <stdio.h>

int main()
{
    int arr[] = {1, 2, 3};
    printf("%d\t%d\t%d\t%d\t%d\t%d\n", *arr, arr[0], 0[arr], *(arr + 2), arr[2], 2[arr]);
}
%%bash

clang -Wall array_pointer.c -o array_pointer
./array_pointer

Allocating memory for 2D arrays

%%file array_2d.c
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int r = 3, c = 4;

    // first allocate space for the pointers to all rows
    int **arr = malloc(r * sizeof(int *));
    // then allocate space for the number of columns in each row
    for (int i=0; i<r; i++) {
        arr[i] = malloc(c * sizeof(int));
    }

    // fill array with integer values
    for (int i = 0; i <  r; i++) {
        for (int j = 0; j < c; j++) {
             arr[i][j] =i*r+j;
        }
    }

    for (int i = 0; i <  r; i++) {
      for (int j = 0; j < c; j++) {
         printf("%d ", arr[i][j]);
        }
    }

    // every malloc should have a free to avoid memory leaks
    for (int i=0; i<r; i++) {
        free(arr[i]);
    }
    free(arr);
}
%%bash

gcc -Wall array_2d.c -o array_2d
./array_2d

More on pointers

Differnt kinds of nothing : There is a special null pointer indicated by the keyword NULL that points to nothing. It is typically used for pointer comparisons, since NULL pointers are guaranteed to compare as not equal to any other pointer (including another NULL). In paticular, it is often used as a sentinel value to mark the end of a list. In contrast a void pointer (void *) points to a memory location whose type is not decalred. It is used in C for generic operations - for example, malloc returns a void pointer. To totally confuse the beginning C student, there is also the NUL keyword, which refers to the '\0' character used to terminate C strings. NUL and NULL are totally differnet beasts.

Deciphering pointer idioms : A common C idiom that you should get used to is *q++ = *p++ where p and q are both pointers. In English, this says

  • *q = *p (copy the variable pointed to by p into the variable pointed to by q)
  • increment q
  • increment p
%%file pointers3.c
#include <stdio.h>
#include <stdlib.h>

int main()
{
    // example 1
    typedef char* string;
    char *s[] = {"mary ", "had ", "a ", "little ", "lamb", NULL};
    for (char **sp = s; *sp != NULL; sp++) {
        printf("%s", *sp);
    }
    printf("\n");

    // example 2
    char *src = "abcde";
    char *dest = malloc(5); // char is always 1 byte by C99 definition

    char *p = src + 4;
    char *q = dest;
    while ((*q++ = *p--)); // put the string in src into dest in reverse order

    for (int i = 0; i < 5; i++) {
        printf("i = %d, src[i] = %c, dest[i] = %c\n", i, src[i], dest[i]);
    }
}
%%bash

clang -Wall pointers3.c -o pointers3
./pointers3

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文