Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save hbarcelos/50660a31bb4ff7c18a68a07aba3b4cf6 to your computer and use it in GitHub Desktop.
Save hbarcelos/50660a31bb4ff7c18a68a07aba3b4cf6 to your computer and use it in GitHub Desktop.
Parameter Passing: C vs Solidity Complete Guide

Parameter Passing: C vs Solidity Complete Guide

Core Concepts

Parameter Passing Mechanisms

  • Pass by Value/Copy: A copy of the value is made and passed to the function. Changes inside the function don't affect the original variable.
  • Pass by Reference: The function receives a reference (alias) to the original variable. Changes directly modify the original.

Traditional Languages: C

C uses pass by value for all function parameters:

void modify(int x) {
    x = 100;  // Only modifies the local copy
}

int main() {
    int num = 5;
    modify(num);
    printf("%d", num);  // Still prints 5
    return 0;
}

To achieve reference-like behavior, explicitly pass pointers:

void modify(int* x) {
    *x = 100;  // Modifies the value at the address
}

int main() {
    int num = 5;
    modify(&num);  // Pass address of num
    printf("%d", num);  // Now prints 100
    return 0;
}

Solidity: Type-Dependent Behavior

Value Types (Pass by Value)

  • uint, int, bool, address, bytes32, etc.
  • Always copied - original unchanged
function modify(uint x) internal {
    x = 100;  // Only modifies local copy
}

uint num = 5;
modify(num);  // num is still 5

Reference Types (Pass by Reference)

  • arrays, structs, mappings, strings
  • Passed by reference for storage and memory
function modifyArray(uint[] memory arr) internal {
    arr[0] = 100;  // Modifies the original array
}

uint[] memory numbers = new uint[](3);
numbers[0] = 5;
modifyArray(numbers);  // numbers[0] is now 100

Data Locations in Solidity

Storage References

// Storage reference - modifies original state
function modifyStorage(uint[] storage arr) internal {
    arr[0] = 100;  // Changes blockchain state
}

Memory References

// Memory reference - modifies temporary data
function modifyMemory(uint[] memory arr) internal pure {
    arr[0] = 100;  // Modifies memory copy
}

Calldata References

// Calldata - read-only reference
function readCalldata(uint[] calldata arr) external pure {
    // arr[0] = 100;  // Error: cannot modify calldata
    uint value = arr[0];  // Can only read
}

Reference Reassignment Behavior

Storage References: Cannot Reassign ❌

function tryReassignStorage() public {
    uint[] storage localRef = storageArray;
    
    localRef[0] = 100;  // ✅ Works - modifying contents
    // localRef = new uint[](5);  // ❌ Compilation error!
}

Error: "TypeError: Type uint256[] memory is not implicitly convertible to expected type uint256[] storage pointer."

Memory References: Can Reassign ✅

function reassignMemory(uint[] memory arr) public pure returns (uint[] memory) {
    arr[0] = 100;        // Modify original contents
    arr = new uint[](5); // ✅ Reassign to new array
    arr[0] = 200;
    return arr;          // Returns the new array
}

Calldata References: Cannot Reassign ❌

function tryReassignCalldata(uint[] calldata arr) external pure {
    // arr[0] = 100;     // ❌ Cannot modify
    // arr = new uint[](5); // ❌ Cannot reassign
    uint value = arr[0]; // ✅ Can only read
}

Memory Persistence and Multiple References

Key Insight: Memory Locations Remain Valid After Reassignment

function demonstrateReassignment() public pure returns (uint[] memory, uint[] memory) {
    // Create initial array
    uint[] memory arr = new uint[](3);
    arr[0] = 100;
    arr[1] = 200;
    arr[2] = 300;
    
    // Save reference to original location
    uint[] memory originalRef = arr;
    
    // Reassign arr to point to new memory
    arr = new uint[](2);
    arr[0] = 999;
    arr[1] = 888;
    
    // Both memory locations are still valid!
    return (originalRef, arr); // Returns ([100, 200, 300], [999, 888])
}

Multiple References Example

function complexMemoryTest() public pure returns (
    uint[] memory,
    uint[] memory, 
    uint[] memory
) {
    uint[] memory first = new uint[](2);
    first[0] = 111;
    first[1] = 222;
    
    uint[] memory second = first;  // Same memory location
    uint[] memory third = first;   // Same memory location
    
    second[0] = 999;  // Changes first[0] and third[0] too!
    
    second = new uint[](1);  // Only second points elsewhere now
    second[0] = 777;
    
    return (first, second, third);
    // Returns: ([999, 222], [777], [999, 222])
}

Summary Comparison

Aspect C Solidity
Default behavior Always pass by value Depends on data type
Reference semantics Manual (via pointers) Automatic for reference types
Memory management Manual Automatic with gas costs
Data location Not specified storage, memory, calldata
Reassignment Pointer can be reassigned Depends on data location

Reference Reassignment Rules

Data Location Can Reassign? Can Modify Contents? Notes
Storage ❌ No ✅ Yes Fixed aliases to storage slots
Memory ✅ Yes ✅ Yes Movable pointers in memory
Calldata ❌ No ❌ No Read-only function parameters

Key Takeaways

  1. C keeps it simple: Uniform pass-by-value with manual pointer management for reference behavior.

  2. Solidity is type-aware: Automatically chooses passing mechanism based on data types.

  3. Memory safety: Unlike C, Solidity prevents dangling references and memory corruption.

  4. Storage references are fixed: Cannot be reassigned to prevent accidental state modifications.

  5. Memory persistence: Reassigning a memory reference doesn't invalidate the original memory location.

  6. Gas implications: Understanding reference behavior is crucial for gas optimization in Solidity.

The fundamental difference is that C gives you complete control with manual memory management, while Solidity provides automatic memory safety with explicit data location controls for optimization and security.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment