此篇為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;
}