Skip to content

Common Mistakes

This page lists some common mistakes that many contestants frequently make in competitions.

Errors Caused by Environment Differences

  • Using %I64d format specifier with scanf or printf on Linux may cause input/output format errors.

Errors That Cause CE

These errors are mostly lexical, syntactic, and semantic errors. They are relatively simple to cause and easy to fix.

Examples:

  • Spelling errors like writing int main() as int mian().

  • Forgetting to add a semicolon after writing a struct or class.

  • Declaring an array too large, using illegal functions (e.g., multithreading) on OJ, or declaring but not defining a function, which causes linking errors.

  • Mismatched function parameter types.

    • Example: When using the max function from the <algorithm> header, passing an int type parameter and a long long type parameter.

      1
      2
      3
      4
      // query is a custom function returning long long
      printf("%lld\n", max(0, query(1, 1, n, l, r));
      
      // Error: no overloaded function "std::max" matches the argument list
      
  • Using goto and switch-case to skip the initialization of some local variables.

Errors That Do Not Cause CE but Cause Warnings

Programs with such errors can compile but are likely to produce incorrect results. These errors are pointed out by the compiler when compiling with the -W{warningtype} parameter.

  • Confusing the assignment operator = with the comparison operator ==.

    • Example:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      std::srand(std::time(nullptr));
      int n = std::rand();
      if (n = 1)
        printf("Yes");
      else
        printf("No");
      
      // Regardless of the random value of n, the output will always be Yes.
      // Warning: Incorrect operator: constant assignment performed in Boolean context. Consider using '==' instead.
      
    • If you indeed want to use = where == should be used (e.g., while (foo = bar)), and you don't want to receive a warning, you can use double parentheses: while ((foo = bar)).

  • Errors due to operator precedence.

    • Example:

      1
      2
      3
      4
      5
      6
      // Wrong
      // std::cout << (1 << 1 + 1);
      // Correct
      std::cout << ((1 << 1) + 1);
      
      // Warning: '<<': check for possible operator precedence error; use parentheses to clarify precedence
      
  • Incorrect use of the static modifier.

  • Forgetting to add the address-of operator & when using scanf for input.

  • Mismatch between parameter types and format specifiers when using scanf or printf.

  • Using bitwise operators and the logical operator == together without parentheses.

    • Example: (x >> j) & 3 == 2
  • Integer literal overflow for int.

    • Example: long long x = 0x7f7f7f7f7f7f7f7f, 1<<62.
  • Uninitialized local variables.

    What happens with uninitialized variables?

    Original text: https://loj.ac/d/3679 by @hly1204

    For example, when we declare an int a; in C++ without initializing it, sometimes we might think a is a "random" (though not truly random) value, or we might think it's a fixed value, but that's not actually the case.

    In a simple test code:

    https://wandbox.org/permlink/T2uiVe4n9Hg4EyWT

    The code is:

    1
    2
    3
    4
    5
    6
    7
    #include <iostream>
    
    int main() {
      int a;
      std::cout << std::boolalpha << (a < 0 || a == 0 || a > 0);
      return 0;
    }
    

    With optimizations enabled on some compilers and environments, the output is false.

    If interested, you can read https://www.ralfj.de/blog/2019/07/14/uninit.html, although it uses Rust for experiments, the essence is the same.

  • Local variables having the same name as global variables, causing the global variables to be accidentally overwritten. (Enable -Wshadow to check for such errors.)

  • Output errors caused by operator overloading.

    • Example:

      1
      2
      3
      4
      5
      6
      // Intention: The first << is the overloaded operator for output; the second << is the shift operator, shifting 1 left by 1 bit.
      // But due to forgetting parentheses, the compiler treats the second << as an output operator as well, resulting in output different from expected.
      // Wrong
      std::cout << 1 << 1;
      // Correct
      std::cout << (1 << 1);
      

Errors That Cause Neither CE nor Warnings

These errors cannot be detected by the compiler and must be identified by the programmer.

Errors That Cause WA

  • Not clearing arrays before reading the next set of data after processing the previous set.

  • Fast input optimization not handling negative numbers.

  • Insufficient bit width of the data type used, causing overflow.

    • As described by the saying "Three years of OI, all in vain, without long long you'll see your ancestors." Contestants fail to score because they didn't use long long (defining integers as long long type) in the right places.
  • When storing a graph, node numbering starts from 0, but the edges given in the problem have endpoints numbered from 1, forgetting to subtract 1 when reading.

  • Mistyping or reversing greater-than/less-than signs.

  • Mixing scanf/printf and std::cin/std::cout IO after executing ios::sync_with_stdio(false);, causing input/output disorder.

    • Example:

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      // This example demonstrates the consequences of mixing two IO methods after disabling synchronization with stdio.
      // It's recommended to step through to observe the effect.
      #include <cstdio>
      #include <iostream>
      
      int main() {
        // After disabling synchronization, cin/cout will use independent buffers instead of synchronizing output to scanf/printf's buffer, reducing IO overhead.
        std::ios::sync_with_stdio(false);
        // With cout, when using '\n' for newline, content is buffered and not output immediately.
        std::cout << "a\n";
        // printf's '\n' flushes printf's buffer, causing misaligned output.
        printf("b\n");
        std::cout << "c\n";
        // The cout buffer is output only when the program ends.
        return 0;
      }
      
  • Errors due to macro expansion without parentheses.

    • Example: This macro returns \(2+2\times 2+2 = 8\) instead of \(4^2 = 16\).

      1
      2
      #define square(x) x* x
      printf("%d", square(2 + 2));
      
  • Computational errors in hashing due to not using unsigned.

    • Right-shifting negative numbers fills the highest bit with 1. See: Bit Operations
  • Not removing or commenting out debug output statements.

  • Accidentally adding an extra ;.

    • Example:

      1
      2
      3
      /* clang-format off */
      while (1);
          printf("OI Wiki!\n");
      
  • Incorrect sentinel value setting. For example, the 0 node in a balanced tree.

  • When initializing variables with : in a class or struct constructor, the declaration order of variables does not match the dependency order during initialization.

    • The initialization order of member variables is related to their declaration order in the class, not the order in the initialization list. See: "Initialization order" in Constructors and member initializer lists
    • Example:

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      #include <iostream>
      
      class Foo {
       public:
        int a, b;
      
        // a will be initialized before b, its value is indeterminate.
        Foo(int x) : b(x), a(b + 1) {}
      };
      
      int main() {
        Foo bar(1, 2);
        std::cout << bar.a << ' ' << bar.b;
      }
      
      // Possible output: -858993459 1
      
  • Not merging the ancestors of two elements when merging sets in a union-find.

    • Example:

      1
      2
      f[a] = b;              // Wrong
      f[find(a)] = find(b);  // Correct
      
  • Using a for append mode in freopen

    • CCF's testing environment does not clear the output file. Using a will cause the previous contestant's output to be read by the judge, leading to WA.

Different Newline Characters

Warning

In official competitions, efforts are made to ensure the contestant's environment matches the final testing environment.

This section applies only to practice contests, etc., and we also recommend problem setters to make data conform to the Data Format.

Different operating systems use different symbols to mark newlines. Here are the newline characters for several commonly used systems:

  • LF (represented by \n): Unix or Unix-compatible systems

  • CR+LF (represented by \r\n): Windows

  • CR (represented by \r): Mac OS version 9 and earlier

C/C++ uses the escape sequence \n for newline, which may lead us to believe that newline characters in input are also represented by \n. Reading only one character to represent a newline may cause incomplete reading of the input file.

Solutions:

  • Use getchar() multiple times until the desired character is read.

  • Use cin for input, which may increase code constant.

  • Use scanf("%s",str) to read a string, then take str[0] as the read character.

  • Use scanf(" %c",&c) to filter out all whitespace characters.

Errors That Cause Undefined Results

Undefined behavior leads to unknown results, which could be WA, RE, etc. Compilers usually assume your program does not have undefined behavior, so code behavior may differ with and without O2 optimization.

  • Division by 0 (finding the inverse of 0)

    Example
    1
    cout << x / 0 << endl;
    
  • Array (index) out of bounds

    For example:

    • Incorrect loop initial value causing access to index -1.

    • Undirected graph edge list not opened to twice the size.

    • Segment tree not opened to 4 times the space.

    • Misreading data range, missing a zero.

    • Incorrectly estimating algorithm space complexity.

    • Calling pushup or pushdown on leaf nodes in a segment tree.

      Correct approach: Do not go out of bounds, check your code to ensure index accesses x are within defined bounds.

  • Non-main function with a return value executes to the end without any return statement

    Even if one branch has a return, but others do not, the result is undefined.

    You can add -Wall to compilation options to check if the compiler gives warnings about functions not returning.

  • Attempting to modify string literals

    Example
    1
    2
    3
    char *p = "OI-wiki";
    p[0] = 'o';
    p[1] = 'i';
    

    Attempting to modify string literals causes undefined behavior. Use other appropriate data types, such as std::string and char[].

  • Multiple deallocations / illegal dereferencing of memory

    For example:

    • Dereferencing a pointer without initialization.

    • Pointer points to already freed memory.

      When using erase, delete, or free, be careful not to use them multiple times on the same address/object.

  • Attempting to free part of memory allocated by new []

    For example:

    1
    2
    3
    4
    5
    6
    object *pool = new object[POOL_SIZE];
    
    object *pointer = pool + 10;
    
    // Error!
    delete pointer;
    

    Common when using a memory pool to pre-allocate a block of memory, then trying to use delete or free() to release a single object obtained from the pool.

  • Dereferencing null/wild pointers

    For null pointers: First check for null, using p == nullptr or !p.

    For wild pointers: Set the pointer to nullptr when freeing to avoid.

  • Signed integer overflow

    For example, we have an expression x+1 > x.

    The normal output should be true, but when x is INT_MAX, the output is false. This is called signed integer overflow.

    Use larger data types (e.g., long long or __int128), or check for overflow. If negative numbers are guaranteed not to occur, unsigned integers can also be used.

    Signed integer overflow may affect compiler optimization. For example, the code:

    1
    2
    3
    4
    int foo(int x) {
      if (x > x + 1) return 1;
      return 0;
    }
    

    May be optimized by the compiler directly to:

    1
    int foo(int x) { return 0; }
    

    Because the compiler can assume signed integers never overflow, so x > x + 1 never holds.

  • Using uninitialized variables

    Example
    1
    2
    3
    4
    5
    int foo(int a) {
      int t; /* not initialized */
      if (/* using */ t > 3) return a;
      return 0;
    }
    

Errors That Cause RE

  • Not removing file operations (on some OJs).

  • Incorrect comparison function for sorting: std::sort requires a strict weak ordering: a<a is false; if a<b is true, then b<a is false; if a<b is true and b<c is true, then a<c is true. Pay special attention to the second point. If these requirements are not met, sorting may cause RE. For example, when writing parity sorting for Mo's algorithm, this is wrong:

    1
    2
    3
    4
    5
    6
    bool operator<(const int a, const int b) {
      if (block[a.l] == block[b.l])
        return (block[a.l] & 1) ^ (a.r < b.r);
      else
        return block[a.l] < block[b.l];
    }
    

    In the above code, (block[a.l]&1)^(a.r<b.r) does not satisfy the second requirement. Changing it to this is correct:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    bool operator<(const int a, const int b) {
      if (block[a.l] == block[b.l])
        // Wrong: does not satisfy strict weak ordering requirements
        // return (block[a.l] & 1) ^ (a.r < b.r);
        // Correct
        return (block[a.l] & 1) ? (a.r < b.r) : (a.r > b.r);
      else
        return block[a.l] < block[b.l];
    }
    
  • Insufficient stack space on Windows, causing stack overflow. Windows sends a SIGSEGV signal to the program, terminating it and returning 3221225725 (i.e., 0xC00000FD, NTSTATUS defined as STATUS_STACK_OVERFLOW).
    If using the gcc compiler, you can add the command -Wl,--stack=SIZE during compilation to specify the stack size limit, where SIZE is the stack size in bytes.

    Insufficient stack space on Linux causes stack overflow. Linux writes head_info randomly in the stack heap, which in most cases causes the program to exit immediately, displaying messages like segmentation fault (core dumped).
    You can use ulimit -s SIZE in the terminal to modify the stack size limit for the current terminal, where SIZE is the stack size in kilobytes (KB).
    Note: If you set the stack size limit too large, infinite recursion may cause the recursion stack to become too large and crash the system.

Errors That Cause TLE

  • Not checking boundaries in divide-and-conquer, causing infinite recursion.

  • Infinite loop.

    • Loop variable name conflict.

    • Loop direction reversed.

  • Not marking whether a state has been visited in BFS.

  • Using macro expansion to write min/max

    This error can greatly increase program runtime and even directly affect code time complexity. It is especially common among beginners writing segment trees.

    A common wrong way:

    1
    2
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    

    Although correct in terms of correctness, if you directly take max of function return values, like a = Max(func1(), func2()), and the function runtime is long, it will greatly affect program performance because after macro expansion it becomes a = func1() > func2() ? func1() : func2(), calling the function three times, one more than the normal max function. Note: If func1() returns different answers each time, this max writing may also cause errors. For example, func1() is return ++a; and a is a global variable.

    Example: The following code can be slowed down to \(\Theta(n)\) per query, causing TLE.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    #define max(x, y) ((x) > (y) ? (x) : (y))
    
    int query(int t, int l, int r, int ql, int qr) {
      if (ql <= l && qr >= r) {
        ++ti[t];  // Record node visit count for debugging
        return vi[t];
      }
    
      int mid = (l + r) >> 1;
      if (mid >= qr) return query(lt(t), l, mid, ql, qr);
      if (mid < ql) return query(rt(t), mid + 1, r, ql, qr);
      return max(query(lt(t), l, mid, ql, qr), query(rt(t), mid + 1, r, ql, qr));
    }
    
  • Using the + operator to append characters to a std::string class string

    This error creates a temporary string variable, modifies it, then assigns it back to the original variable. This error cannot be optimized by the compiler and may cause time complexity degradation with large data.

    Common wrong way:

    1
    2
    3
    std::string a;
    char b = 'c';
    a = a + b;
    

    When executing this code, the program first creates a temporary string variable, stores the value of a into it, appends b at the end, then stores it back into a.

    From the assembly result, a = a + b calls three functions from std::__cxx11::basic_string: operator+, operator=, and variable creation.

    The correct way should be:

    1
    2
    3
    std::string a;
    char b = 'c';
    a += b;
    

    This way directly appends character b to string a, calling operator+= only once. For a more detailed performance comparison, refer to Benchmark.

  • Not removing file operations (on some OJs).

  • Repeatedly executing functions with complexity not \(O(1)\) in for/while loops. Strictly speaking, this may change time complexity.

  • Incorrect midpoint formula or termination condition in binary search.

Errors That Cause MLE

  • Array too large.

    Detailed Explanation of Memory Usage Indicators on Linux

    TL;DR: If you declare a particularly large global static array during CCF-style exams, be especially careful. Because the array declared by the program is fully counted in memory usage (unlike most online judges which only count actual usage), in some cases this may even cause the entire problem to MLE.

    • About RSS and VSZ12

      1. VSZ (Virtual Memory Size)3

        VSZ represents the virtual memory size of a process, the total size of virtual address space the process can access, usually displayed in KB.

        Virtual memory is a logical concept, usually much larger than actual memory usage.

        On Linux, you can use the top command to view the memory composition of a process, where the VIRT column represents its virtual memory.

        Virtual memory generally includes address space allocated but not actually used by the process. Simply put, virtual memory is approximately what you requested.

        It is particularly important to note that common online judges usually only count physical memory usage. But CCF's judging environment counts virtual memory, meaning if you declare a large global static array, even if you only use a small part, it occupies a lot of space.

      2. RSS (Resident Set Size)4

        RSS represents the actual physical memory size occupied by a process, i.e., the size of page frames resident in RAM, usually displayed in KB.

        Similarly, you can use top to view a process's physical memory in the RES column.

        RSS generally only includes parts actually loaded into physical memory, meaning actual usage is what counts.

    • Memory Usage Behavior Analysis

      Assume the following array is declared:

      1
      2
      const int SIZE = 1e8;
      int arr[SIZE];  // Space occupied: 4 bytes * 100 million = 400 MB
      

      This is a static array allocated in the global data segment. This array is not explicitly initialized, usually allocated in the BSS segment (if explicitly initialized (e.g., all zeros or other values), allocated in the DATA segment).

      • When the array is completely unused (assuming the compiler does not optimize it away)

        • Physical memory: If the array is not accessed, demand paging mechanism prevents memory pages from being loaded into physical memory. Physical memory does not increase or increases only slightly (maybe some metadata pages are loaded).
        • Virtual memory: The array size is counted in virtual memory (increases by 400MB), because the entire array's virtual address space has been allocated.
      • When partially using the array

        Assume only a few elements of the array are used, e.g.:

        1
        2
        arr[0] = 1;
        arr[999999] = 2;
        
        • Virtual memory: Virtual memory does not change, still 400MB.
        • Physical memory: Each time an array element is accessed, the corresponding virtual page is loaded into physical memory. Assuming system page size is 4KB, each page contains \(4 \text{KB} ÷ 4 \text{B} = 1024\) int elements. Accessing the array twice may load 2 pages, increasing physical memory by about \(2 \times 4 \text{KB} = 8 \text{KB}\).
      • When most of the array is used

        Assume assigning values to the first \(50,000,000\) elements:

        1
        2
        3
        for (int i = 0; i < 50000000; ++i) {
          arr[i] = i;
        }
        
        • Virtual memory (VSZ): VSZ remains 400MB, unchanged.
        • Physical memory (RSS): This is sequential access (the accessed \(50,000,000\) elements are adjacent in memory addresses), the number of pages to load is \(\left\lceil \dfrac{50,000,000}{1024} \right\rceil = 48,828\) pages.

          Assuming each page is 4KB, total is \(48,828 \times 4 \text{KB} \approx 190 \text{MB}\), physical memory increases to about 190MB.

          Note: If assigning values to the array with random indices, physical memory usage may differ significantly from prediction (because memory pages are loaded based on address, causing random assignments to load many memory pages).

        Brief summary: As the proportion of accessed parts increases, physical memory approaches virtual memory (assuming no page reclamation).

    • Inserting too many elements into STL containers.

    • Often due to an infinite loop in a loop that inserts elements into STL.

    • Possibly being deliberately exploited (e.g., anti-hash tests).

Errors That Cause Large Constant Factors

  • Not defining modulus as a constant.

    • Example:

      1
      2
      // int mod = 998244353;      // Wrong
      const int mod = 998244353;  // Correct, helps compiler treat it as a constant
      
  • Using unnecessary recursion (tail recursion is not included here).

  • Introducing a large amount of extra computation when converting recursion to iteration.

Errors That Only Affect Local Execution

  • Possible errors with file operations:

    • Not closing file pointer fclose(fp) before setting fp = fopen() again during stress testing. This creates many file wild pointers in the process.

    • Not adding .in/.out to filenames in freopen().

  • Forgetting to delete or free after using heap space.

References and Notes