Короткий ответ: потому что в C (и по умолчанию в C++) все параметры функций передаются по значению. Указатель — это просто переменная, которая хранит адрес; при передаче вы копируете его значение (адрес). Чтобы изменить саму переменную-указатель в вызывающем коде, нужно передать её адрес (указатель на указатель) или в C++ — ссылку на указатель.
Пояснение на примерах.
1) Передача указателя по значению (копия указателя):
```c
void set_null(int *p) {
p = NULL; // меняется локальная копия p
}
int *q = malloc(sizeof(int));
set_null(q);
// q всё ещё указывает на выделенную память
```
Здесь p внутри функции — отдельная переменная, и присваивание p = NULL не влияет на q.
2) Изменить оригинал — передать адрес указателя (int **):
```c
void set_null_ptr(int **pp) {
*pp = NULL; // меняем значение указателя в вызывающем коде
}
int *q = malloc(sizeof(int));
set_null_ptr(&q);
// теперь q == NULL
```
3) В C++ можно проще — ссылка на указатель:
```cpp
void set_null_ref(int *&p) {
p = nullptr; // меняем сам указатель в вызывающем коде
}
```
Важно: если вы передаёте указатель по значению, то изменять через него можно содержимое памяти, на которое он указывает:
```c
void set_value(int *p) {
*p = 42; // изменяет данные по адресу, поэтому видимо в вызывающем коде
}
int x = 0;
set_value(&x);
// x == 42
```
Ещё момент: при передаче массива в функцию имя массива преобразуется в указатель на первый элемент — опять же копируется адрес. Поэтому внутри функции вы можете менять элементы массива, но не «переименовать» сам массив в вызывающей функции.
Итог: указатель передаётся как значение, потому что так устроена семантика параметров в C (и по умолчанию в C++). Если нужно изменить сам указатель у вызывающего — передайте указатель на указатель (int **) или в C++ — ссылку на указатель (int *&), либо верните новый указатель из функции и присвойте его в вызывающем коде.