Fortran DEALLOCATE - pointers

I am currently trying to code a little subroutine in Fortran to deallocate all allocated variables in memory when my program comes to an error, i.e., a failed to load file or an inexistent needed file. At this point, execution must be terminated, but not all allocatables are necessarily allocated (it depends on where on the code the error appeared), so i can't make a cleanup deallocating all of them.
My current approach is the following:
SUBROUTINE Cleanup(A)
REAL(8), ALLOCATABLE, DIMENSION(:) :: A
IF (ALLOCATED(A)) THEN
DEALLOCATE(A)
END IF
END SUBROUTINE
and call "Cleanup" for every allocatable. The problem with this is that not all my variables are dimension-1. I have up to three dimensions in some of them.
I first thought about writing 3 different subroutines for different dimensions and using overloading, but this do not seems to be very elegant.
Then it came to my mind that maybe i could pass a pointer instead the actuall argument A, but i've googled and it seems you can't deallocate a target variable trough a pointer.
Any ideas about how to do this properly?
Thanks.

My approach to this would use a combination of the following:
As of Fortran 95, all local un-saved allocatable variables that are allocated when a procedure finishes are automatically deallocated. Whether this is applicable depends on how your DLL is called, and hence whether you can actually structure things such that all your allocatables are unsaved locals.
As of Fortran 2003 (or Fortran 95 + the allocatable TR - this language level is widely supported amongst maintained Fortran compilers) allocatable actual arguments passed to INTENT(OUT) allocatable dummy arguments will be automatically deallocated before the procedure starts execution. Your Cleanup routine in the question just needs to add the declaration of the dummy argument as INTENT(OUT) and then there's no need for the IF test or DEALLOCATE. You still need to write the routine for each type and rank that you need to clean up.
Similar to the previous, allocatable components of derived type variables passed to an INTENT(OUT) dummy argument will be automatically deallocated. So you may be able to collect all your allocatable variables together as components in an object of derived type. Cleanup then simply involves passing that object to a procedure with an INTENT(OUT) dummy. INTENT(OUT) here also resets components that have default initialization back to their "default" value. Perhaps there's other cleanup that you need to manually do at this point too (close files, etc).
An alternative approach, again using derived types with all your variables as components, is to make the derived type object itself allocatable. When you need to cleanup, simply deallocate that one object - components of it will be automatically deallocated. Fortran 2003 allows for a final procedure to be triggered from this sort of event if you have additional other cleanup to do at this point.
A derived type approach also makes it easy to have multiple instances of whatever your DLL supports independently active at the one time (you just have multiple objects of derived type).
Examples of the derived type approach, given:
TYPE MyType
REAL, ALLOCATABLE :: variable_one(:)
INTEGER, ALLOCATABLE :: variable_two(:)
...
END TYPE MyType
INTENT(OUT) dummy
TYPE(MyType) :: object
ALLOCATE(object%variable_one(xxx))
ALLOCATE(object%variable_two(yyy))
...
IF (things_have_gone_wrong) CALL Cleanup(object)
...
SUBROUTINE Cleanup(arg)
TYPE(MyType), INTENT(OUT) :: arg
END SUBROUTINE Cleanup
ALLOCATABLE object.
TYPE(MyType), ALLOCATABLE :: object
ALLOCATE(object)
ALLOCATE(object%variable_one(...))
ALLOCATE(object%variable_two(...))
...
IF (things_have_gone_wrong) DEALLOCATE(object)

Related

what are the considerations for pointers/copies for OMP parallelized fortran program

I am working on a large program that I have parallelized with OMP, and in several places for the parallelization I have to make a choice between using pointers or copies of arrays of data. I had an intuitive sense that the choice might have an impact on compute time, but I am uncertain. In the example program given below, I am unable to discern a difference.
compile with:
gfortran paralleltest.f90 -fopenmp
run with:
>>> ./a.out 8 10000000
Copies (s): 0.835768998
results: 8415334.9722864032 8415334.9722864032 8415334.9722864032 8415334.9722864032 8415334.9722864032 8415334.9722864032 8415334.9722864032 8415334.9722864032
Pointers (s): 0.837329030
results: 8415334.9722864032 8415334.9722864032 8415334.9722864032 8415334.9722864032 8415334.9722864032 8415334.9722864032 8415334.9722864032 8415334.9722864032
Given this example I am wondering about some questions:
in general would one expect to see no difference between pointers and copies used in this say?
what circumstances, if any, would lead to significant differences between the two, assuming that the memory overhead of the copies was not an issue.
am I measuring this performance difference in the right way? and if not, what is a better way to measure the performance?
Here is the fortran file:
! paralleltest.f90
module m_parallel_test
implicit none
type :: t_data
real, allocatable :: x(:)
end type t_data
type :: t_data_ptr
real, pointer :: x(:)
end type t_data_ptr
contains
real(kind=8) function aggregate_data(x) result(value)
real :: x(:)
integer :: i
do i=1,size(x)
value = value + cos(x(i))
end do
end function aggregate_data
subroutine associate_pointer(parent, ptr)
real, target :: parent(:)
real, pointer :: ptr(:)
ptr => parent
end subroutine associate_pointer
end module m_parallel_test
program main
use m_parallel_test
implicit none
character(len=32) :: arg
integer, parameter :: n_set = 8
integer :: n_threads, n_data, i
type(t_data) :: parent
type(t_data), allocatable :: copies(:)
type(t_data_ptr), allocatable :: pointers(:)
real(kind=8), allocatable :: copies_results(:)
real(kind=8), allocatable :: pointers_results(:)
real :: start_time, stop_time
call get_command_argument(1, arg)
read(arg, *) n_threads
call omp_set_num_threads(n_threads)
allocate(copies(n_set))
allocate(pointers(n_set))
allocate(copies_results(n_set))
allocate(pointers_results(n_set))
call get_command_argument(2, arg)
read(arg, *) n_data
allocate(parent%x(n_data))
call seed_rng(1)
call random_number(parent%x)
do i=1,n_set
copies(i)%x = parent%x
call associate_pointer(parent%x, pointers(i)%x)
end do
call cpu_time(start_time)
!$OMP PARALLEL DO
do i=1,n_set
copies_results(i) = aggregate_data(copies(i)%x)
end do
call cpu_time(stop_time)
print *, "Copies (s): ", stop_time - start_time
print *, "results: ", copies_results
call cpu_time(start_time)
!$OMP PARALLEL DO
do i=1,n_set
pointers_results(i) = aggregate_data(pointers(i)%x)
end do
call cpu_time(stop_time)
print *, "Pointers (s): ", stop_time - start_time
print *, "results: ", pointers_results
end program main
subroutine seed_rng(i)
integer, intent(in) :: i
integer, allocatable :: iseed(:)
integer :: n, j
call random_seed(size=n)
allocate(iseed(n))
iseed= [(i + j, j=1, n)]
call random_seed(put=iseed)
end subroutine seed_rng
The code is not conforming for two reasons:
the function result value in aggregate_data is not defined before being referenced.
in the main program, the pointer component x of the elements of pointers have undefined pointer association status when passed to the aggregate_data procedure, on account of the first actual argument in the calls to associate_pointer not having the TARGET attribute.
(You can call a procedure with a dummy argument that has the TARGET attribute with a corresponding actual argument that does not, but when you do, pointers associated with the dummy argument (such as ptr inside associate_pointer) become undefined when execution of the procedure completes.)
The errors in the program can be corrected by giving the parent variable in the main program the TARGET attribute, and by sticking an appropriate assignment statement prior to the loop in aggregate_data.
Beyond correctness, the only difference between the two loops is in the actual argument passed to aggregate_data - one loop passes an array designated by an allocatable component, one loop passes a array designated by a pointer component. The procedure takes that array argument as an assumed shape array dummy argument without CONTIGUOUS - given typical implementation of assumed shape arrays there's no need for the compiler to do anything different passing pointer vs allocatable.
(If the dummy argument of aggregate_data was explicitly or implicitly contiguous, there may be a need for the compiler to copy the pointer array prior to the call as pointers can be associated with non-contiguous arrays. Allocatable arrays are always contiguous.)
Both loops write their result to an non-target allocatable array of different kind to the actual argument components - so there cannot be any aliasing across the assignment statement.
Neither actual argument is being modified within the loops, so potential aliasing issues associated with the pointer arguments designating the same array in different iterations are moot (besides which, from the compiler's perspective - that's a guarantee that is on the head of the programmer - it doesn't have to care).
All up, I'd expect the same machine instructions to be issued for the two loops. Similar timing is unsurprising.
Results may differ if different work is done in the loops - if aliasing or contiguity comes into play.
Where the approaches will differ is in the data copying required to set the allocatable array components arguments up. But that is (intentionally?) outside that timing regime.
As of Fortran 2003 on (which this code requires), do not use POINTER's unless you need reference semantics.

Passing Fortran POINTER to SUBROUTINES as MODULE PROCEDURE

My first question is, "Can POINTER be passed to subroutines next to INTENT?"
Although passing Fortran POINTER with INTENT(IN) or INTENT(INOUT) seems to be a common practice, the documentation of Intel for Fortran POINTER says(https://software.intel.com/content/www/us/en/develop/documentation/fortran-compiler-developer-guide-and-reference/top/language-reference/a-to-z-reference/o-to-p/pointer-fortran.html)
The pointer cannot be declared with the INTENT or PARAMETER attributes
which confuses me a lot.
My second question is related to following code.
I tried overloading write_matrix with write_matrix_2d - which is for 2d arrays - and write_matrix_2d_ptr - which is for 2d Fortran POINTERs.
It turned out the code fails to compile with
Ambiguous generic interface WRITE_MATRIX: previously declared specific procedure WRITE_MATRIX_2D is not distinguishable from this declaration. [WRITE_MATRIX_2D_PTR]
If I comment (1) then it complies, and runs fine ( but I suppose this is bad practice since POINTER arguments going into subroutien write_matrix_2d which expects array input.)
Is there any way to overload Fortran subroutines just by their arguments' datatype just like C/C++?
module mod_write
implicit none
interface write_matrix
module procedure write_matrix_2d
(1) !module procedure write_matrix_2d_ptr
end interface
contains
subroutine write_matrix_2d(a)
implicit none
real, intent(in) :: a(:,:)
! local variables
integer :: i, j
print *, "in ARRAY printing subroutine"
do i = lbound(a,1), ubound(a,1)
write(*,*) ( a(i,j), j = lbound(a,2), ubound(a,2) )
enddo
end subroutine
subroutine write_matrix_2d_ptr(a)
implicit none
real, pointer, intent(in) :: a(:,:)
! local variables
integer :: i, j
print *, "in POINTER printing subroutine"
do i = lbound(a,1), ubound(a,1)
write(*,*) ( a(i,j), j = lbound(a,2), ubound(a,2) )
enddo
end subroutine
end module
program test
use mod_write
implicit none
real, dimension(9) :: b = (/21, 22, 23, 24, 25, 26, 27, 28, 29/)
real, dimension(3,3) :: c
real, dimension(3,3), target :: ct(3,3)
real, dimension(:,:), pointer :: cptr(:,:)
c = reshape( b, (/3, 3/) )
write(*,*)
write(*,*) 'c'
call write_matrix(c)
(2) !call write_matrix_2d(c)
ct = c
cptr => ct(2:3,2:3)
write(*,*)
write(*,*) 'ct'
call write_matrix(ct)
(2,3)!call write_matrix_2d_ptr(ct)
write(*,*) 'cptr'
call write_matrix(cptr)
(2) !call write_matrix_2d_ptr(cptr)
end program
My third question is again related to the attached code. Overloading is not that big a deal since I can just use write_matrix_2d and write_matrix_2d_ptr separately as (2)s in the code. But, as you can see at (3), call write_matrix_2d_ptr(ct) runs fine with the target input ct while write_matrix_2d_ptr expects pointer input. Is this okay, or should I make another write_matrix_2d_target?
Thank you.
The compiler documentation is wrong (or badly out of date). A dummy argument that is a pointer can also have the intent attribute. The attribute applies to the pointer association status of the argument, not the target.
Procedures in a generic interface can be overloaded if their dummy arguments are distinguishable. Dummy arguments are distinguishable if they have different type, kind or rank. Whether something is a pointer or not is not part of its type. (To be complete, arguments are also distinguishable if one is a pointer without intent(in) and the other is allocatable.) The requirements for distinguishable dummy arguments for different procedures in a generic interface mean that the rules around which specific procedure a particular generic reference resolves to are very simple.
Your example of overloading would be ambiguous - it is fine to pass a(n associated) pointer actual argument to a non-pointer dummy - the actual argument designates the target of the pointer. It is also permitted to pass a non-pointer actual with the target attribute to an intent(in) pointer dummy.
Whether something is a pointer or not is completely orthogonal to whether it is an array or not.
Pointers in Fortran are primarily used to reference different things (targets) during program execution.
Dummy arguments only need to be a pointer if you need to do something inside the procedure that relates to the association status of the pointer - perhaps you want to test the association status, or associate the pointer with something else.
(1) The intent restriction is some copy and paste error or it is in some strange context I do not understand. There was such a resteiction in Fortran 90.
(2) "but I suppose this is bad practice since POINTER arguments going into subroutien write_matrix_2d which expects array input."
There is nothing bad about passing a Fortran pointer variable as an ordinary variable. The default argument passing mechanism is that the target of thee pointer is passed using the normal non-ointer mechanism. One only uses pinter dummy arguments if one has specific reasons to do so. Most often tbat means than one works with the association status inside the subroutine. There are some other uses but you need a specific reason to declare your argument as pointer, otherwise it just complicates things.
(3) target can be passed to pointer, intent(in) in Fortran 2008.

Fortran calls: advantage of passing by pointer vs passing by reference

It is my understanding that in Fortran arrays are passed by reference. So is there an advantage to passing a pointer to a large array (into a subroutine) as opposed to passing the array itself.
Could you also clarify this in the context of recursive functions. I have seen implementations where pointers are used "for efficiency", but if everything is passed by reference, then what's the benefit of pointers.
Here's an example. I have an array X (in reality lets say it's a very large array).
INTEGER :: X(:)
I can define a subrouitne that takes this array as follows:
SUBROUTINE FOO(X)
INTEGER, INTENT(IN) :: X(:)
INTEGER :: I
DO I = 1, 4
WRITE(*,*) X(I)
ENDDO
END SUBROUTINE FOO
When I call the subroutine above then the array X is not copied as fortran passes a reference to it. Now lets say I have a modified version of the subroutine:
SUBROUTINE FOO2(X)
INTEGER, POINTER, INTENT(IN) :: X(:)
INTEGER :: I
DO I = 1, 4
WRITE(*,*) X(I)
ENDDO
END SUBROUTINE FOO2
I can call FOO2 from a program as follows:
PROGRAM TEST
IMPLICIT NONE
INTEGER, TARGET :: X(5)
INTEGER, POINTER :: Y(:)
X = (/1,2,3,4,5/)
Y => X
CALL FOO2(Y)
END PROGRAM TEST
Then here's my question: is there a performance difference between the two versions of foo? Is there any useful scenario where the declaration of FOO2 might be preferable to FOO?
In this simple case there shouldn't be any real difference. Note the program is illegal, you don't have the explicit interface to FOO or FOO2, but I will assume you just ommited it for simplicity and they are in a module or internal.
Both arrays can be non-contiguous in principle, so no difference here. If that slows down the code, the contiguous attribute might help. Or assumed size or explicite size arrays too.
Your subroutine is too simple, so there is no danger of aliasing too. This is the common source of decreasing performance with pointers. There could be potential aliasing with some other argument or another variable you access by host or use association provided it has the target attribute.
The purpose of pointer arguments is actually to either allow disassociated (null()) arguments, or to allow changing of the association status in the subroutines. Your example doesn't use neither and therefore the pointer attribute is superfluous.
There is on last small difference. It is not specified in the standard what is actually passed to the subroutine at the machine code level for the pointer variables. If it is just an address (likely for scalars) it is the same as non-pointer, just the aliasing rules and the allowed usage are different. Otherwise some descriptor is passed, but any overhead should be negligible, the assumed shape arrays use a descriptor too.

Fortran 90 difference between compaq visual fortran and gfortran

This may be a specific question, but I think it pertains to how memory is handled with these two compilers (Compaq visual Fortran Optimizing Compiler Version 6.5 and minGW). I am trying to get an idea of best practices with using pointers in Fortran 90 (which I must use). Here is an example code, which should work "out of the box" with one warning from a gfortran compiler: "POINTER valued function appears on RHS of assignment", and no warnings from the other compiler.
module vectorField_mod
implicit none
type vecField1D
private
real(8),dimension(:),pointer :: x
logical :: TFx = .false.
end type
contains
subroutine setX(this,x)
implicit none
type(vecField1D),intent(inout) :: this
real(8),dimension(:),target :: x
logical,save :: first_entry = .true.
if (first_entry) nullify(this%x); first_entry = .false.
if (associated(this%x)) deallocate(this%x)
allocate(this%x(size(x)))
this%x = x
this%TFx = .true.
end subroutine
function getX(this) result(res)
implicit none
real(8),dimension(:),pointer :: res
type(vecField1D),intent(in) :: this
logical,save :: first_entry = .true.
if (first_entry) nullify(res); first_entry = .false.
if (associated(res)) deallocate(res)
allocate(res(size(this%x)))
if (this%TFx) then
res = this%x
endif
end function
end module
program test
use vectorField_mod
implicit none
integer,parameter :: Nx = 15000
integer :: i
real(8),dimension(Nx) :: f
type(vecField1D) :: f1
do i=1,10**4
f = i
call setX(f1,f)
f = getX(f1)
call setX(f1,f)
if (mod(i,5000).eq.1) then
write(*,*) 'i = ',i,f(1)
endif
enddo
end program
This program runs in both compilers. However, changing the loop from 10**4 to 10**5 causes a serious memory problem with gfortran.
Using CTR-ALT-DLT, and opening "performance", the physical memory increases rapidly when running in gfortran, and doesn't seem to move for the compaq compiler. I usually cancel before my computer crashes, so I'm not sure of the behavior after it reaches the maximum.
This doesn't seem to be the appropriate way to use pointers (which I need in derived data types). So my question is: how can I safely use pointers while maintaining the same sort of interface and functionality?
p.s. I know that the main program does not seem to do anything constructive, but the point is that I don't think that the loop should be limited by the memory, but rather it should be a function of run-time.
Any help is greatly appreciated.
This code has a few problems, perhaps caused by misunderstandings around the language. These problems have nothing to do with the specific compiler - the code itself is broken.
Conceptually, note that:
There is one and only one instance of a saved variable in a procedure per program in Fortran 90.
The variable representing the function result always starts off undefined each time a function is called.
If you want a pointer in a calling scope to point at the result of a function with a pointer result, then you must use pointer assignment.
If a pointer is allocated, you need to have a matching deallocate.
There is a latent logic error, in that the saved first_entry variables in the getX and setX procedures are conflated with object specific state in the setX procedure and procedure instance specific state in the getX procedure.
The first time setX is ever called the x pointer component of the particular this object will be nullified due to the if statement (there's an issue of poor style there too - be careful having multiple statements after an if statement - it is only the first one that is subject to the conditional!). If setX is then called again with a different this, first_entry will have been set to false and the this object will not be correctly set-up. I suspect you are supposed to be testing this%TFX instead.
Similarly, the first time getX is called the otherwise undefined function result variable res will be nullified. However, in all subsequent calls the function result will not be nullified (the function result starts off undefined each execution of the function) and will then be erroneously used in an associated test and also perhaps erroneously in a deallocate statement. (It is illegal to call associated (or deallocate for that matter) on a pointer with an undefined association status - noting that an undefined association status is not the same thing as dissociated.)
getX returns a pointer result - one that is created by the pointer being allocated. This pointer is then lost because "normal" assignment is used to access the value that results from evaluating the function. Because this pointer is lost there can't be (and so there isn't...) a matching deallocate statement to reverse the pointer allocation. The program therefore leaks memory. What almost certainly should be happening is that the thing that captures the value of the getX function in the main program (f in this case, but f is used for multiple things, so I'll call it f_ptr...) itself should be a pointer, and it should be pointer assigned - f_ptr => getX(f1). After the value of f_ptr has been used in the subsequent setX call and write statement, it can then be explicitly deallocated.
The potential for accidental use of normal assignment when pointer assignment is intended is one of the reasons that use of functions with pointer results is discouraged. If you need to return a pointer - then use a subroutine.
Fortran 95 simplifies management of pointer components by allowing default initialization of those components to NULL. (Note that you are using default initialization in your type definition - so your code isn't Fortran 90 anyway!)
Fortran 2003 (or Fortran 95 + the allocatable TR - which is a language level supported by most maintained compilers) introduces allocatable function results - which remove many of the potential errors that can otherwise be made using pointer functions.
Fortran 95 + allocatable TR support is so ubiquitous these days and the language improvements and fixes made to that point are so useful that (unless you are operating on some sort of obscure platform) limiting the language level to Fortran 90 is frankly ridiculous.

pointer as a dummy argument

I am somewhat puzzled by the following program
module test
implicit none
type TestType
integer :: i
end type
contains
subroutine foo(test)
type (TestType), intent(out) :: test
test%i = 5
end subroutine
subroutine bar(test)
type (TestType), intent(out) :: test
test%i = 6
end subroutine
end module
program hello
use test
type(TestType) :: t
call foo(t)
print *, t%i
call bar(t)
print *, t%i
end program hello
and its derivatives. More on those later. As we know, Fortran transfers routine arguments as a pass-by-reference, meaning that the entity emerging at the dummy argument test for both foo and bar is the same memory space granted on the stack in program hello. So far so good.
Suppose I define in program hello the type(TestType) :: t as a pointer, and allocate it.
program hello
use test
type(TestType), pointer :: t
allocate(t)
call foo(t)
print *, t%i
call bar(t)
print *, t%i
deallocate(t)
end program hello
The code works as before, the only difference being that the object was not allocated on the stack, but on the heap.
Now assume to go back to the stack-allocated program and that subroutine bar is instead defined as
subroutine bar(test)
type (TestType), pointer :: test
test%i = 6
end subroutine
The program does not compile anymore because you must use the heap-allocated version to make it work, or to be more accurate it is mandatory to pass a pointer to the routine when the routine is defined to accept a pointer as a dummy argument. On the other hand, if the dummy argument does not contain the pointer keyword, the routine would accept both pointers and non-pointers.
This makes me wonder... what's the point of declaring a dummy argument a pointer ?
Reposted from comp.lang.fortran, an answer by Tobias Burns:
Now assume to go back to the stack-allocated program and that
subroutine bar is instead defined as
subroutine bar(test)
type (TestType), pointer :: test
test%i = 6
end subroutine
The program does not compile anymore because you must use the
heap-allocated version to make it work,
That's not quite correct: You can also not pass an ALLOCATABLE variable
to a dummy with POINTER attribute. I think one (practical) reason is
that the pointer address can escape and you would thus cause alias
problems. A formal reason is that an ALLOCATABLE is simply not a
POINTER; additionally, the standard does not talk about heap vs. stack
vs. static memory. And in fact, local arrays [with constant bounds] will
often be created in static memory and not on the stack (unless you use
OpenMP or the RECURSIVE attribute). Thus, your "stack" example could
also be a "static memory" example, depending on the compiler and the
used options.
or to be more accurate it is
mandatory to pass a pointer to the routine when the routine is defined
to accept a pointer as a dummy argument.
That's also not completely true. In Fortran 2008 you can pass a
non-POINTER, which has the TARGET attribute, to a pointer dummy which
has the INTENT(IN) attribute. (Pointer intent is relative to the pointer
association status; for non-pointer dummies the intents are about the
value stored in the variable.)
This makes me wonder... what's the point of declaring a dummy argument
a pointer ?
Well, if the argument has the POINTER attribute, you can allocate and
free the pointer target, you can associate the pointer with some target
etc. Up to Fortran 95 it was not possible to have ALLOCATABLE dummy
arguments thus a pointer had to be used if a (dummy) argument had to be
allocated in a procedure.
If you can, you should try to use rather ALLOCATABLEs than POINTERs -
they are easier to use, do not leak memory and have pose no
alias-analysis problems to the compiler. On the other hand, if you want
to create, e.g., a linked list, you need a pointer. (Though, for a heap
usage, also Fortran 2008's allocatable components could be used.*)
*I mean:
type t
type(t), allocatable :: next
end type
where the component is of the same type as the type being defined;
before F2008 this was only allowed for pointers but not for allocatables.
and by R. Maine
As we know, Fortran transfers routine arguments as a
pass-by-reference,
We apparently know incorectly, then. The standard never specifies that
and, indeed goes quite a lot out of its way to avoid such specification.
Although yours is a common misconception, it was not strictly accurate
even in most older compilers, particularly with optimization turned on.
A strict pass-by-reference would kill many common optimizations.
With recent standards, pass-by-reference is all but disallowed in some
cases. The standard doesn't use those words in its normative text, but
there are things that would be impractical to implement with
pass-by-reference.
When you start getting into things like pointers, the error of assuming
that everything is pass-by-reference will start making itself more
evident than before. You'll have to drop that misconception or many
things wil confuse you.
I think other people have answered the rest of the post adequately. Some
also addressed the above point, but I wanted to emphasize it.
Hope this answers your question.

Resources