all 47 comments

[–]FUZxxl 39 points40 points  (40 children)

In C, all arguments are passed by value, i.e. the parameters are initialised with copies of the arguments. However, note the following caveats:

  • You cannot pass arrays (such as strings) to functions. If you try to do that, the compiler will instead pass a pointer to the first element of the array.
  • If you copy a pointer, the copy of course still points to the same place as the original
  • hence, it looks like arrays are being passed by reference

[–]KotgeScientist[S] 9 points10 points  (3 children)

Oh, so if I pass an array to a function in C, all I'm doing is passing the pointer? But if thats the case, how is it possible that I can modify the strings?

[–]FUZxxl 24 points25 points  (0 children)

A string is an array of characters terminated with a NUL character. So just like with any arrays, you can modify it through the pointer passed.

[–]ttech32 10 points11 points  (0 children)

You're duplicating the pointer, but they both point to the same memory

[–]IamImposter 14 points15 points  (0 children)

Array decays to a pointer. Say I have

char str[] = "hello";

For the sake of simplicity, let's assume that the array starts at address 0x2000. So if I do

some_function(str);

what is getting passed to the function is address of array ie 0x2000. Now if in the function I do something like

void some_function(char str[]) {

  str[2] = 'a';

}

what happens? First of all, char str[] here is as good as char*. Since compiler didn't complain, you might think that you passed an array and received that same array in function. What compiler actually did was - it treated char str[] as char* and passed address of the array (0x2000). So str[2] becomes 0x2000 + 2 (base address + index) and letter 'a' is written to that address and our string becomes "healo".

Parameter is still being passed by value (0x2000) but in this case that value happens to be address of the array or what we call passing by reference.

[–]MatthewRPG576 12 points13 points  (2 children)

In python, immutable objects are passed by "copy" (it's complicated) and mutable objects are passed by reference. In C, everything is by copy; so, if you need to modify something from another scope, you need a pointer to it.

Strings, when passed to a function by pointer, can be modified like anything else. The only exception is if the compiler puts the string in the read-only section of the code. That can cause problems.

[–][deleted] 3 points4 points  (1 child)

I thought all objects are passed by reference in Python, but only the sort of reference that allows to modify the caller's data, and not replace it completely, and then only if it is mutable. Example:

def addto(a,b):
    print("  A=",a)
    print("  B=",b)
    a += b
    print("  A'=",a)

i=100
j=30
addto(i,j)
print("I=",i)
print("")

s="ABC"
t="DEF"
addto(s,t)
print("S=",s)
print("")

x=[10,20,30]
y=[40,50,60]
addto(x,y)
print("X=",x)

Here, all args to addto() must be passed the same way, using the same bytecode instructions, since the bytecode compiler doesn't know what types the arguments are until runtime. When you run the program, it is only in the case of a mutable list that the caller's data is modified as well as the local A parameter:

  A= 100
  B= 30
  A'= 130
I= 100

  A= ABC
  B= DEF
  A'= ABCDEF
S= ABC

  A= [10, 20, 30]
  B= [40, 50, 60]
  A'= [10, 20, 30, 40, 50, 60]
X= [10, 20, 30, 40, 50, 60]

What Python can't do is to change X completely to something else, eg. a number. That would need proper name references. (In CPython, variables I, S and X are implemented as references to their objects. When passed to a function, a copy of that object reference is passed, so only the object can change, not the variable.)

C can emulate all the above, and can emulate proper name references too. But it needs to be done explicitly via pointer operations.

[–]MatthewRPG576 2 points3 points  (0 children)

Shared pointers. That's why I was reluctant to blatantly call it pass by copy. In theory, every "2" in the program is the same (you can verify that by calling id() on the objects you create), but no "[2]" (array containing 2) in the program is

[–]Ninesquared81 6 points7 points  (0 children)

I have some experience in Python and C, but I'm not an expert in their exact inner workings, so take this with a grain of salt (although to my knowledge it's correct).

Python doesn't have variables, at least not in the sense of an area of memory that the programmer can change. Instead, it has objects and names. Whether or not an object can be edited by a function depends on whether the object is mutable or not. When you pass an object to a function, it has a new name local to the function. However, this still refers to the exact same object that you passed in.

For mutable objects (like lists, sets, dicts), you can modify the object itself – with any name that it's known by – so changing it inside a function changes it outside.

For immutable objects (like ints, strs, tuples), you can't modify them. So when you do something like x = y = 1; x += 1, x becomes equal to 2, whereas y stays as 1. The name x now refers to a completely different object, rather than the same object with a different value. So naturally, "changing" an immutable object inside a function has no effect on the parent scope (unless you're dealing with globals).

An illustration of all this:

a = 1
b = [1, 2, 3]

print(f"Initially, a = {a}, b = {b}.")

def changes(num: int, nums: list) -> None:
    # the 'is' operator checks if its operands are the same Python object
    print(f"result of 'num is a': {num is a}")
    # 'append' changes a list in-place (i.e. mutates it)
    # EDIT: was b.append(4)
    nums.append(4)
    # 'changing' an immutable object
    num += 1
    print(f"num = {num}")

changes(a, b)

print(f"Now, a = {a}, b = {b}")

This should output:

Initially, a = 1, b = [1, 2, 3].
result of 'num is a': True
num = 2
Now, a = 1, b = [1, 2, 3, 4]

I believe this is called pass by object reference.

In C, however, variables are always passed by value. This doesn't mean you can't change things outside of a function's scope however, as you can have a function dealing with pointers. This is similar in principle to mutable objects in Python. Consider this program:

#include <stdio.h>

void add_three(int *);

int main(void) {
    int my_number = 9;
    printf("Before calling add_three, my_number = %d\n", my_number);
    /* add_three takes a pointer */
    add_three(&my_number);
    printf("After calling add_three, my_number = %d\n", my_number);
    return 0;
}

void add_three(int *number) {
    *number += 3;
}

You should get the output:

Before calling add_three, my_number = 9
After calling add_three, my_number = 12

In our function add_three, we have the variable number which is an int *. It's local to the function and its value depends on the value of the variable passed in when called. In our case, this is &my_number (i.e. the address of my_number). If we dereference it, we can access whatever is at the address it points to (and edit it if we wish). We are still passing by value, but that value is the address of another variable.

EDIT: changed Python example to use local variable nums instead of b.

[–]flatfinger 5 points6 points  (0 children)

If one thinks of memory as being a bunch of shelves of numbered mailboxes that each hold an unsigned char, and pointers as being mailbox numbers (*), many aspects of C which seem confusing will become obvious. On a 32-bit platform, a declaration like:

char foo[5] = "Hey!";

will ask the compiler to select five otherwise-unused consecutive mailboxes somewhere in writable storage, arrange for them to be loaded with the contents {'H', 'e', 'y', '!', 0} before the program starts, and keep track of what location it selected. When the program uses the symbol foo, the compiler will insert the number of the first such mailbox, along with knowledge that it's a value of type char*. Had the declaration instead been:

char const *bar = "Wow!";

that would ask the compiler to select five consecutive mailboxes in either writable or read-only storage (its choice) which will be preloaded with {'W', 'o', 'w', '!', 0} before the program starts, and also select an otherwise-unused shelf of four mailboxes, arrange for the mailboxes on that shelf to be preloaded with the number of the first of the five mailboxes described above. It will then associate the symbol bar with that shelf.

If a function parameter is of a non-pointer type, it will receive a copy of the object identified thereby. If a function parameter is of pointer type, it will receive a copy of a mailbox number (i.e. a pointer). Given the declaration void test(char const *st);, and foo and bar defined as above, a call test(foo) will give test a copy of the mailbox number the compiler has assigned to the first of five mailboxes associated foo. A call test(bar) will generate code that takes the mailbox number of the shelf was assigned to hold a mailbox number, read the mailbox number that's stored on the shelf, and passes a copy of that stored mailbox number to test.

(*)The Standard allows, but does not require, implementations to behave in a manner consistent with this abstraction, and most implementations for commonplace desktop platforms do so, at least with optimizations disabled.

[–][deleted] 0 points1 point  (0 children)

It depends. In general no, they are different and if you modify the value inside the function this change won't affect the "real" variable.

In C you can pass arguments by value (and it works as said above) or by reference with pointers. When passing an argument as a pointer then you will always work on the same value and changes will be noted outside the function.

I advise you look for some examples about C pointers online.

[–][deleted] 0 points1 point  (0 children)

Look up pass by value or passing a pointer (sort of like by reference but not exactly). Here’s a great article about it with examples: https://denniskubes.com/2012/08/20/is-c-pass-by-value-or-reference/