C 語言練習程式(4) -- 指標相關程式集錦(3)


Posted by nathan2009729 on 2022-07-23

此篇為Pointers in C Programming A Modern Approach to Memory Management, Recursive Data Structures, Strings, and Arrays這本書第三章筆記。

簡單概論

程式1:function-arguments.c

#include <stdio.h>

void doesnt_mutate(int i)
{
  i += 42;
}

void mutates(int *i)
{
  *i += 42;
}

void foo(int *ip)
{
  ip++;
}

void bar(int **ip)
{
  (*ip)++;
}

int main(void)
{
  int i = 0;
  doesnt_mutate(i);
  printf("i is %d\n", i);
  mutates(&i);
  printf("i is %d\n", i);

  int *ip = 0;
  foo(ip);
  printf("%p\n", ip);
  bar(&ip);
  printf("%p\n", ip);

  return 0;
}

void bar(int **ip)。想修改指標,就需要二級指標(pointer of pointer)。
To change an object, you need a reference to it.

程式2:point.c

#include <stdio.h>

typedef struct point {
  double x, y;
} point;

point move_point_horisontally(point p, double amount)
{
  p.x += amount;
  return p;
}

point move_point_vertically(point p, double amount)
{
  p.y += amount;
  return p;
}

point move_point(point p/*1 copy*/, double delta_x, double delta_y)
{
  p = move_point_horisontally(p, delta_x);/*2 copies*/
  p = move_point_vertically(p, delta_y);/*2 copies*/
  return p;/*1 copy*/
}

void print_point(point p)
{
  printf("point <%.2f, %.2f>\n", p.x, p.y);
}

typedef struct rectangle {
  point upper_left;
  point lower_right;
} rectangle;

rectangle move_rectangle(rectangle rect,/*2 copies*/
                         double delta_x,
                         double delta_y)
{
  rect.upper_left =
    move_point(rect.upper_left, delta_x, delta_y);/*6 copies*/
  rect.lower_right =
    move_point(rect.lower_right, delta_x, delta_y);/*6 copies*/
  return rect;/*2 copies*/
}

void print_rectangle(rectangle rect)
{
  printf("rectangle:\n");
  print_point(rect.upper_left);
  print_point(rect.lower_right);
  printf("\n");
}

int main(void)
{
  point p = { .x = 0.0, .y = 0.0 };
  print_point(p);

  p = move_point(p, 10, 10);
  print_point(p);
  printf("\n");

  rectangle rect = {
    .upper_left =  {.x =  0.0, .y = 10.0},
    .lower_right = {.x = 10.0, .y =  0.0}
  };
  print_rectangle(rect);

  rect = move_rectangle(rect, 10, 10);
  print_rectangle(rect);

  return 0;
}

想像一下移動方形的程式。如果不用指標就是上述寫法,但會有一堆值的複製。比如point move_point()這函數就需要6次複製,而呼叫兩次point move_point()rectangle move_rectangle()就需要12次以上複製。

另外,可以看看assign的方式

typedef struct point {
  double x, y;
} point;
point p = { .x = 0.0, .y = 0.0 };
typedef struct rectangle {
  point upper_left;
  point lower_right;
} rectangle;
rectangle rect = {
    .upper_left =  {.x =  0.0, .y = 10.0},
    .lower_right = {.x = 10.0, .y =  0.0}
  };

以下改寫為指標版
程式3:pointer-point.c

#include <stdio.h>

typedef struct point {
  double x, y;
} point;

void move_point_horisontally(point *p, double amount)
{
  p->x += amount;
}

void move_point_vertically(point *p, double amount)
{
  p->y += amount;
}

void move_point(point *p, double delta_x, double delta_y)
{
  move_point_horisontally(p, delta_x);
  move_point_vertically(p, delta_y);
}

void print_point(point *p)
{
  printf("point <%.2f, %.2f>\n", p->x, p->y);
}

typedef struct rectangle {
  point upper_left;
  point lower_right;
} rectangle;

void move_rectangle(rectangle *rect,
                    double delta_x,
                    double delta_y)
{
  move_point(&rect->upper_left, delta_x, delta_y);
  move_point(&rect->lower_right, delta_x, delta_y);
}

void print_rectangle(rectangle *rect)
{
  printf("rectangle:\n");
  print_point(&rect->upper_left);
  print_point(&rect->lower_right);
  printf("\n");
}

int main(void)
{
  point p = { .x = 0.0, .y = 0.0 };
  print_point(&p);

  move_point(&p, 10, 10);
  print_point(&p);
  printf("\n");

  rectangle rect = {
    .upper_left =  {.x =  0.0, .y = 10.0},
    .lower_right = {.x = 10.0, .y =  0.0}
  };
  print_rectangle(&rect);

  move_rectangle(&rect, 10, 10);
  print_rectangle(&rect);

  return 0;
}

程式4:pointers-to-dead-objects.c

#include <math.h>
#include <float.h>
#include <stdio.h>

typedef struct vector {
  double x;
  double y;
  double z;
} vector;

void print_vector(vector const *v)
{
  double x = v->x, y = v->y, z = v->z;
  printf("<%.2f, %.2f, %.2f>\n", x, y, z);
}

double vector_length(vector *v)
{
  double x = v->x, y = v->y, z = v->z;
  return sqrt(x*x + y*y * z*z);
}
vector *shortest(int n, vector *vectors[n])
{
  vector *shortest = &(vector){
    .x = DBL_MAX, .y = DBL_MAX, .z = DBL_MAX
  };
  double shortest_length = vector_length(shortest);

  printf("%p %p\n", (void *)shortest, (void *)&shortest_length);

  for (int i = 0; i < n; ++i) {
    vector *v = vectors[i];
    double length = vector_length(v);
    if (length < shortest_length) {
      shortest = v;
      shortest_length = length;
    }
  }

  return shortest;
}

void trash_stack(void)
{
  volatile char x[1000];
  for (int i = 0; i < 1000; i++) {
    x[i] = 0;
  }
}

int main(void)
{
  vector *vectors[] = {
    &(vector){ .x = 10.0, .y = 13.0, .z = 42.0 },
    &(vector){ .x = -1.0, .y = 32.0, .z = 15.0 },
    &(vector){ .x =  0.0, .y =  3.0, .z =  1.0 }
  };

  print_vector(shortest(3, vectors));
  print_vector(shortest(2, vectors));
  print_vector(shortest(1, vectors));
  print_vector(shortest(0, vectors)); // BOOOM!!!

  vector *v = shortest(0, vectors);
  print_vector(v);
  trash_stack();
  print_vector(v);

  return 0;
}

請注意print_vector(shortest(0, vectors)); // BOOOM!!!這一行。
如果傳0,那麼return的變數會是區域變數,這樣的變數,雖然它的地址與值會一直存在,但卻可能

會因為call別的函數,使得它的值被覆寫。這是個bug,但編譯器不會察覺。
所以之後的code

vector *v = shortest(0, vectors);
  print_vector(v);
  trash_stack();
  print_vector(v);

就在模擬此bug。

所以要在shortest函數來加入防呆裝置,就會用到NULL變數。
程式5:null.c

#include <math.h>
#include <float.h>
#include <stdio.h>

typedef struct vector {
  double x;
  double y;
  double z;
} vector;

double vector_length(vector *v)
{
  double x = v->x, y = v->y, z = v->z;
  return sqrt(x*x + y*y * z*z);
}

void print_vector(vector const *v)
{
  if (!v) {
    printf("NULL\n");
  } else {
    double x = v->x, y = v->y, z = v->z;
    printf("<%.2f, %.2f, %.2f>\n", x, y, z);
  }
}

vector *shortest(int n, vector *vectors[n])
{
  if (n < 1) return 0; // Return a NULL pointer

  vector *shortest = vectors[0];
  double shortest_length = vector_length(shortest);
  for (int i = 1; i < n; ++i) {
    vector *v = vectors[i];
    double length = vector_length(v);
    if (length < shortest_length) {
      shortest = v;
      shortest_length = length;
    }
  }

  return shortest;
}

int main(void)
{
  vector *vectors[] = {
    &(vector){ .x = 10.0, .y = 13.0, .z = 42.0 },
    &(vector){ .x = -1.0, .y = 32.0, .z = 15.0 },
    &(vector){ .x =  0.0, .y =  3.0, .z =  1.0 }
  };

  print_vector(shortest(3, vectors));
  print_vector(shortest(2, vectors));
  print_vector(shortest(1, vectors));
  print_vector(shortest(0, vectors)); // BOOOM!!!

  vector const longest = {
     .x = DBL_MAX, .y = DBL_MAX, .z = DBL_MAX
   };
   vector const *v = shortest(0, vectors);
   v = v ? v : &longest;
   print_vector(v);


  return 0;
}

防呆:

vector *shortest(int n, vector *vectors[n])
{
  if (n < 1) return 0; // Return a NULL pointer

注意

vector *shortest = &(vector){
    .x = DBL_MAX, .y = DBL_MAX, .z = DBL_MAX
  };

這是指標變數shortest的初始化!!

const專論

先從定義開始。
For any type T, T const is a constant of that type.
For any type T, T * is a pointer to that type.

不合理的assign?

程式6:const3.c

#include <stdio.h>

int main(void)
{
  int i = 42;
  int *ip = &i;
  const int *cp = &i;

  for (int j = 0; j < 10; ++j) {
    i++;
    printf("*ip == %d, *cp == %d\n", *ip, *cp);
  }

  int const x = 42;
  int *ip_x = (int*)&x;
  *ip_x = 13;
  printf("*ip_x == %d", *ip_x);

  return 0;
}

輸出:

*ip == 43, *cp == 43
*ip == 44, *cp == 44
*ip == 45, *cp == 45
*ip == 46, *cp == 46
*ip == 47, *cp == 47
*ip == 48, *cp == 48
*ip == 49, *cp == 49
*ip == 50, *cp == 50
*ip == 51, *cp == 51
*ip == 52, *cp == 52
*ip_x == 13

const有assign的問題。比如:

#include <stdio.h>

int main(void)
{
  int *p = 0;
  int const **q = 0;
  int const i = 42;
  q = (int const **)&p;//created an alias for p in *q.
  *q = &i;
  // Now I have an int alias to an int const!
  printf("&i == %p, *p == %p\n", (void *)&i, (void *)p);
  *p = 5; // DANGER: We are trying to change const int
  // This may or may not actually change i.
  // It is up to the C compiler
  printf("i == %d / %d\n", i, *p);
  return 0;
}

輸出:

&i == 0x7fff5a92f5f4, *p == 0x7fff5a92f5f4
i == 5 / 5

其示意圖如下:

另外一個圖:

根據上圖:
Here, we have an int **pointer p, we assign its address to an int const * const **pointer u, and when we then assign the address of an int const * const object, r, into *q, we have created not just one but two illegal aliases.

程式last:const.c

#include <stdio.h>

int main(void)
{
  int *             i_p   = 0;
  int const *       ic_p  = 0;
  int * const       i_pc  = 0;
  int const * const ic_pc = 0;

#if 0
  i_p = i_p;     // Ok, T * => T *
  ic_p = ic_p;   // Ok, T * => T * (T = U const)
  i_pc = i_pc;   // No! You cannot assign to T const
  ic_pc = ic_pc; // No! You cannot assign to T const
#endif

  //i_p = ic_p; // No! T const * => T *
  i_p = i_pc; // Ok, T * const => T *
  //i_p = ic_pc; // No, there is a T const * => T *

  ic_p = i_p;   // Ok, T * => T const *
  ic_p = i_pc;  // Ok, T const => T then U * => U const *
  ic_p = ic_pc; // Ok, T * const => T * (T = U const)

  // Another layour of indirection
  #if 0
    int **               i_p_p   = 0;
    int const **         ic_p_p  = 0;
    int * const *        i_pc_p  = 0;
    int const * const *  ic_pc_p = 0;
  #endif
  typedef int * T;
  typedef int const * U;
  T *        i_p_p   = 0;
  U *        ic_p_p  = 0;
  T const *  i_pc_p  = 0;
  U const *  ic_pc_p = 0;
  // plus the ones with const last, but they
  // are simply const versions and we learn
  // nothing new from them.

  // We do not have const pointers here, so we can only apply
  // T * => T * and T * => T const *, i.e. add const to the
  // pointed at object. If the pointed at objects have different
  // types then we cannot assign. Call the pointed at objects T and
  // U for the right-hand side and left-hand side, respectively

  // No, no and no
  // i_p_p = ic_p_p; No, T = int *, U = int const *, T != U
  // i_p_p = i_pc_p; No, T = int *, U = int * const, removing const not allowed
  // i_p_p = ic_pc_p; No, T = int *, U = int const * const, T != U

  // No, no and no
  // ic_p_p = i_p_p; No, T = int const *, U = int *, T != U
  // ic_p_p = i_pc_p; No, T = int const *, U = int * const, T != U
  // ic_p_p = ic_pc_p; No, removing const

  i_pc_p = i_p_p; // Ok, T const * => T *, T = int *, U = int *
  // i_pc_p = ic_p_p; No, T = int * const, U = int const *, T != U
  // i_pc_p = ic_pc_p; No, T = int const *, U = int * const, T != U

  // ic_pc_p = i_p_p; No, int const * const != int *
  ic_pc_p = ic_p_p; // Ok, Adding a const to int const *
  //ic_pc_p = i_pc_p; No, int const * const != int * const


  printf("to turn off warning... %p %p %p %p\n",
         (void *)i_p, (void *)ic_p, (void *)i_pc, (void *)ic_pc);
  printf("to turn off warning... %p %p %p %p\n",
         (void *)i_p_p, (void *)ic_p_p, (void *)i_pc_p, (void *)ic_pc_p);


  return 0;
}

#c pointer #const關鍵字







Related Posts

Lidemy HTTP Challenge 全破流程與心得

Lidemy HTTP Challenge 全破流程與心得

 如何交作業

如何交作業

W11_Hash 相關

W11_Hash 相關


Comments