Programming in C

Chapter 13
Testing, Debugging, Performance, and Secure C

Bachelors Program in Electronics and Communication
Programming in C   •   Post Graduate

Testing, Debugging, Performance, and Secure C

Introduction

C remains important in embedded systems, operating systems, compilers, communication equipment, scientific software, and performance-critical services. Its direct access to memory and hardware gives expert programmers considerable control, but also makes defects potentially severe. A single invalid pointer, unchecked length, data race, or integer overflow can produce incorrect results, system failure, information disclosure, or remote code execution.

Reliable C development therefore requires more than compiling a program successfully. It requires a disciplined engineering process involving specification, testing, debugging, performance measurement, defensive programming, code review, static analysis, and security assessment. These activities are related but not interchangeable: testing reveals failures, debugging identifies causes, performance analysis explains resource behaviour, and secure programming reduces exploitable weaknesses.

This chapter treats these activities as an integrated quality-assurance lifecycle. It also emphasizes an essential distinction: a program that passes its tests is not necessarily correct for all inputs, and a program that is fast is not necessarily safe.

Learning Objectives

After studying this chapter, the reader should be able to:

  1. distinguish verification, validation, testing, debugging, profiling, and security analysis;
  2. design unit, integration, system, regression, property-based, and fuzz tests for C programs;
  3. diagnose faults using reproducible experiments, assertions, debuggers, logs, and sanitizers;
  4. analyse time and space performance without confusing measurement with speculation;
  5. explain undefined behaviour, implementation-defined behaviour, and unspecified behaviour;
  6. apply secure C practices for memory, integers, strings, input, concurrency, and interfaces;
  7. evaluate quality using coverage, defect data, risk, and threat modelling; and
  8. construct a defensible engineering response to a realistic C-system failure.

1. A Quality Model for C Software

Software quality has several dimensions. Functional correctness asks whether specified outputs are produced for valid inputs. Robustness asks how the program behaves with invalid, extreme, or unexpected inputs. Reliability concerns the probability of failure during operation. Maintainability concerns the ease of modifying and reviewing the code. Performance concerns time, memory, energy, and communication resources. Security concerns confidentiality, integrity, availability, authenticity, and accountability.

Verification asks, “Are we building the product according to its specification?” Examples include code review, static analysis, and checking invariants. Validation asks, “Does the product satisfy the operational need?” Examples include system tests and field trials. Testing is an evidence-producing activity in which executions are designed to expose failures. Debugging begins after an observed failure and seeks its causal defect.

A useful causal chain is:

Relationship among defect, error, failure, and debugging Defectfault in code or design Errorincorrect internal state Failureobservable wrong behaviour Impactloss or hazard Testing exposes failures; debugging traces them back to defects
Relationship among defect, error, failure, and debugging
Figure: Relationship among defect, error, failure, and debugging

Figure: A defect may propagate through an erroneous state to an observable failure and operational impact.

2. Testing Strategies and Test Design

2.1 Levels of testing

Unit testing tests a small function or module in isolation. Dependencies such as hardware drivers, clocks, files, and network services may be replaced by stubs, mocks, or fakes. Unit tests should be fast, deterministic, and numerous.

Integration testing examines interactions between modules—for example, a packet parser, checksum routine, buffer manager, and transport layer. Many defects occur at boundaries where individually correct components make incompatible assumptions.

System testing exercises the complete executable in an environment close to actual deployment. It tests requirements such as timing, recovery, resource limits, and protocol behaviour. Acceptance testing evaluates whether stakeholders' operational needs are met.

Regression testing reruns earlier tests after a change. A newly discovered defect should normally produce a permanent regression test before the defect is fixed or immediately afterwards.

2.2 Test cases and equivalence classes

A test case should specify preconditions, input, execution conditions, expected result, and postconditions. Test design is stronger when it is based on input classes rather than a few convenient examples. For a function receiving a buffer length from 0 through 1024, useful classes include zero, ordinary positive values, the maximum valid value, one beyond the maximum, and representations that may cause conversion or overflow.

Boundary-value analysis is especially important in C because array indexes, allocation sizes, loop termination, and integer conversions are boundary-sensitive. If a valid range is [0, n−1], test 0, 1, n−2, n−1, n, and negative or otherwise invalid representations where the interface permits them.

A test oracle determines correctness. It may be an exact expected value, a reference implementation, a conservation law, an invariant, or a metamorphic relation. For example, sorting should preserve the input multiset and produce a nondecreasing sequence. These properties are often more powerful than storing expected outputs for every input.

2.3 Assertions and contracts

An assertion expresses a condition that must hold at a particular program point. For example:

assert(buffer != NULL);
assert(length <= capacity);

Assertions are appropriate for programmer assumptions and internal invariants, not for validating hostile external input. They may be disabled in production when NDEBUG is defined, so security checks must remain active independently of assertions. A contract can be described as:

Precondition → execution → postcondition

The arrow indicates a logical relationship, not a performance formula. A precondition states what callers must provide; a postcondition states what the function guarantees if its precondition is satisfied. In safety-critical or high-assurance work, contracts should be reviewed, tested, and where possible formally verified.

2.4 Coverage and its limits

Statement coverage measures executed statements; branch coverage measures exercised decision outcomes; condition coverage examines Boolean subexpressions; and modified-condition/decision coverage is used in some safety standards. Coverage is calculated as:

Coverage (%) = (number of covered items ÷ total applicable items) × 100

The numerator and denominator must refer to the same item type and test configuration. Coverage is evidence of exercised code, not proof of correctness. A test suite can achieve high statement coverage while missing incorrect boundary behaviour, concurrency failures, security properties, or untested combinations of conditions. Coverage targets should therefore be risk-informed rather than treated as a substitute for engineering judgement.

2.5 Property-based testing and fuzzing

Property-based testing generates many inputs and checks general properties. Fuzzing specializes in automatically producing unusual, malformed, or mutation-based inputs. A C parser, decompressor, protocol handler, or command interpreter is an excellent fuzz target. A useful harness should isolate one operation, impose time and memory limits, record the seed, and make failures reproducible.

Fuzzing is most effective when combined with sanitizers, corpus minimization, coverage guidance, and explicit invariants. It does not replace specification-based tests: a fuzzer may discover a crash without knowing that a valid packet was interpreted incorrectly.

3. Debugging as Hypothesis-Driven Investigation

Debugging should be treated as an experiment rather than random code modification. First preserve the failure: record the source revision, compiler options, platform, input, environment, and exact symptom. Then reduce the input to a minimal reproducer. A minimal reproducer sharply lowers the number of possible causes and becomes a permanent regression test.

A disciplined cycle is:

  1. observe and characterize the failure;
  2. formulate competing hypotheses;
  3. instrument or inspect the program to distinguish them;
  4. change one relevant factor at a time;
  5. identify the root cause, not merely the symptom;
  6. implement the smallest justified correction;
  7. rerun the reproducer, regression suite, sanitizers, and relevant performance tests.

3.1 Debuggers and diagnostic information

A symbolic debugger can set breakpoints, step through source, inspect variables, examine stack frames, watch memory, and catch signals. Compile development builds with debug information, commonly using -g, and avoid excessive optimization when source-level stepping is required. Optimized code may reorder, remove, or combine operations, so a displayed variable may not correspond directly to a source expression.

A backtrace is useful only when stack information is trustworthy. Stack corruption, undefined behaviour, aggressive optimization, and missing symbols can make it misleading. Core dumps, structured logs, event timestamps, and thread identifiers are valuable for failures that cannot be reproduced interactively.

3.2 Sanitizers and static analysis

AddressSanitizer (ASan) detects many out-of-bounds accesses, use-after-free errors, double frees, and related memory faults. UndefinedBehaviorSanitizer (UBSan) instruments selected forms of undefined behaviour, such as signed overflow or invalid shifts. ThreadSanitizer (TSan) detects many data races. MemorySanitizer (MSan), where supported, detects some uses of uninitialized memory. These tools impose overhead and are not complete proofs; they should be used in dedicated test builds and continuous integration.

Static analysers inspect code without executing every path. They can identify null dereferences, resource leaks, suspicious conversions, unreachable paths, tainted data flows, and violations of coding standards. Their reports require triage: false positives should be documented, and real defects should be fixed rather than merely suppressed.

3.3 Common C fault classes

  • Uninitialized objects: reading an indeterminate value can produce unpredictable results and, for some types, undefined behaviour.
  • Lifetime errors: using an object after its lifetime ends, freeing memory twice, or returning the address of an automatic object.
  • Bounds errors: indexing outside an array or copying more bytes than the destination can hold.
  • Pointer errors: invalid arithmetic, incorrect ownership, alignment violations, and null dereferences.
  • Integer errors: signed overflow, truncation, wraparound in unsigned calculations, and incorrect signed/unsigned comparisons.
  • Format errors: mismatched printf conversion specifications or uncontrolled format strings.
  • Concurrency errors: data races, deadlock, lost wake-ups, and incorrect publication of shared state.

4. Performance Engineering

Performance engineering begins with a requirement. “Fast” is incomplete unless the metric, workload, platform, and acceptable variability are specified. Relevant metrics include latency, throughput, memory footprint, energy per operation, startup time, and deadline-miss rate.

For a workload of n elements, asymptotic analysis describes growth, while measurement describes actual behaviour on a particular system. A program with complexity O(n log n) may outperform an O(n) program for realistic sizes if the latter has poor locality or expensive constants. Complexity analysis and profiling are complementary.

4.1 Measurement principles

Use representative workloads, warm-up where relevant, repeated trials, and a controlled environment. Report median and high-percentile latency when outliers matter. Distinguish CPU time from elapsed time: elapsed time includes waiting, scheduling, I/O, and contention. Avoid benchmarking a function whose result is optimized away; consume results in a verifiable manner.

A basic speedup measure is:

Speedup = Tbaseline ÷ Toptimized

where T_baseline and T_optimized are measured times for the same workload, platform, correctness conditions, and measurement method. A speedup greater than 1 indicates improvement. If parallel execution uses p processors, efficiency is often defined as:

Efficiency = Speedup ÷ p

This is meaningful only when the parallel workload and processor count are clearly specified. Neither expression captures maintainability, energy, or security costs.

4.2 Profiling and bottlenecks

A profiler may use sampling or instrumentation. Sampling periodically records the instruction location and has relatively low overhead; instrumentation records selected events but may perturb execution. Profilers can reveal hot functions, cache effects, allocation frequency, lock contention, and I/O waits.

The optimization loop is: establish a baseline, profile, form a bottleneck hypothesis, make a localized change, verify functional and security properties, benchmark again, and retain the change only if it provides meaningful benefit. Compiler optimization flags should be used in production builds only after testing, because optimization can expose latent undefined behaviour and can alter timing-sensitive code.

4.3 Memory and cache behaviour

Contiguous data structures often improve spatial locality, while repeated pointer chasing can increase cache misses. Reducing allocation frequency may improve both latency and predictability. However, manual pooling and custom allocators create ownership complexity and must be tested under failure conditions. In embedded systems, stack depth, static memory, DMA constraints, and worst-case execution time may be more important than average throughput.

Performance changes must preserve defined behaviour. “It works with this compiler at this optimization level” is not a substitute for a language-level guarantee. Undefined behaviour invalidates reasoning about both correctness and performance.

5. The C Abstract Machine and Undefined Behaviour

The C standard distinguishes several categories of behaviour. Defined behaviour is specified by the standard. Implementation-defined behaviour is selected by the implementation and documented, such as the representation of some implementation choices. Unspecified behaviour permits one of several possibilities without requiring documentation. Undefined behaviour (UB) imposes no requirements on the implementation once it occurs.

Examples of possible UB include signed integer overflow, accessing an array outside its bounds, dereferencing an invalid pointer, using an object outside its lifetime, violating effective-type or aliasing requirements, and shifting by an invalid count. UB is not merely a likely runtime error; compiler transformations may assume it never occurs and produce surprising results.

Portable C also requires attention to object representation, alignment, integer widths, byte order, character signedness, and the sizes of fundamental types. Use <stdint.h> types such as uint32_t when an exact width is required and use <stddef.h> or size_t for object sizes. Convert external representations explicitly rather than assuming that a host machine's layout is a protocol layout.

6. Secure C Programming

Secure programming is risk management across the complete lifecycle. It begins with threat modelling: identify assets, trust boundaries, entry points, attackers, and consequences. A useful principle is least privilege: code should receive only the authority, memory, and input range it needs.

6.1 Memory safety

Every allocation needs a clear owner, lifetime, and failure path. Check allocation results before dereferencing. Prefer APIs that accept destination capacity and return status. Avoid calculating allocation sizes in a narrow integer type. For an array of count objects of size size, a multiplication should be checked before allocation:

if (count > SIZE_MAX / size) {
    /* reject: multiplication would overflow */
}
void *p = malloc(count * size);

The condition assumes size is nonzero; if zero has a special meaning, that case must be specified separately. After allocation, the product is representable in size_t. The program must still check whether malloc returned NULL and must define what zero-count requests mean.

Set pointers to NULL after freeing only when that assignment improves ownership clarity; it does not repair aliases held elsewhere. Prefer designs that make ownership explicit, such as one responsible release path and documented transfer rules.

6.2 Strings and input

C strings require a terminating null character. A destination of capacity C can hold at most C−1 non-null characters when a terminator is required. Functions such as gets are removed from the language because they cannot enforce a bound. Even bounded functions require careful interpretation: strncpy may omit termination and may pad unnecessarily. Safer interfaces should communicate capacity, length, and truncation explicitly.

Never pass untrusted text as the format string:

printf(user_text);          /* unsafe */
printf("%s", user_text);    /* format string is constant */

Input validation should be based on an allow-list where practical, canonicalize before authorization decisions, reject ambiguous encodings, and check both syntax and semantic ranges. Parsing a number requires checking conversion errors, range, trailing characters, and sign conventions.

6.3 Integer and conversion safety

Unsigned arithmetic wraps modulo 2^N for an N-bit unsigned type, but wraparound is not automatically safe: it may produce an undersized allocation or bypass a limit. Signed overflow is undefined. Mixed signedness can convert a negative value to a very large unsigned value. Use explicit range checks before conversions and choose types that match the domain.

For security-sensitive arithmetic, express checks in a form that cannot itself overflow. For example, to test whether a + b fits in an unsigned maximum M, check a > M - b before computing a + b, assuming a, b, and M are represented in the same suitable unsigned type.

6.4 Concurrency and shared state

A data race occurs when conflicting accesses to shared memory are not properly synchronized. The result may be undefined, not merely an unpredictable scheduling choice. Use mutexes, atomic operations, condition variables, or message passing according to the ownership model. Keep lock ordering consistent to reduce deadlock risk, avoid holding locks across unbounded I/O, and define whether callbacks execute under a lock.

Security consequences include bypassed access checks, corrupted audit records, and denial of service. Race testing should vary scheduling and use dynamic race detection where feasible.

6.5 Cryptography and sensitive data

Do not invent cryptographic algorithms or use general-purpose hashes as password storage. Use reviewed libraries, authenticated encryption, secure random generation, and established password-hashing schemes. Compare secrets with suitable constant-time routines when timing leakage is relevant. Minimize lifetime of sensitive data and consider whether compiler optimizations can remove an intended memory wipe; use a library primitive designed for secure erasure.

Key management, certificate validation, update authenticity, and failure handling are system concerns that cannot be solved by a single C function. Embedded products should also consider debug ports, firmware rollback, fault injection, and secure boot.

7. An Integrated Development and Assurance Workflow

A mature workflow combines prevention, detection, diagnosis, and learning:

StageRepresentative practiceEvidence
Specificationcontracts, threat model, resource limitsreviewed requirements
Designownership model, error policy, interfacesdesign record
Implementationwarnings, safe conversions, defensive APIsclean build
Verificationreview, static analysis, unit testsreports and tests
Dynamic testingintegration, fuzzing, sanitizers, race testsreproducible results
Performancecontrolled benchmarks and profilingbaseline and comparison
Releasedependency, configuration, and security reviewrelease checklist
Operationmonitoring, incident response, patchingpost-release evidence

Compiler diagnostics should be treated seriously. A project may use a strict warning policy, for example enabling broad warnings and promoting selected warnings to errors, while documenting unavoidable platform-specific exceptions. Reproducible builds, pinned dependencies, signed artifacts, and a software bill of materials improve supply-chain accountability.

Code review should ask: What are the ownership and lifetime rules? What happens on partial failure? Are sizes and units explicit? Are external inputs authenticated and bounded? Is concurrency synchronized? Is the error observable and safely handled? Does the test suite exercise boundaries and negative cases? Has a performance claim been measured?

8. Illustrative Case Study: Vulnerable Telemetry Packet Parser

Background

Consider an illustrative embedded telemetry gateway receiving packets over a serial link. A packet contains a one-byte type, a two-byte payload length, a payload, and a checksum. The gateway must remain available despite malformed traffic.

Problem

The original parser read the length into an unsigned integer, allocated a fixed 256-byte local buffer, copied the stated number of bytes, and then computed the checksum. It assumed that the sender was trusted. A malformed length caused a stack overwrite; some lengths also caused the receiver to wait indefinitely for bytes that would never arrive. A performance patch later removed a bounds check to reduce copying.

Analysis

Testing with ordinary packets passed. Boundary tests, fuzzing, and ASan exposed the overwrite. Code review identified three independent weaknesses: failure to enforce a protocol maximum, insufficient distinction between received bytes and claimed bytes, and a blocking wait without a deadline. The threat model showed that the serial port was physically accessible in the deployment environment, so malformed input had a realistic denial-of-service and code-execution risk.

Approach and solution

The revised design first reads a fixed header, validates the type and length against a protocol maximum, checks that the complete frame is available within a deadline, and verifies the checksum before dispatch. Length arithmetic is performed in a sufficiently wide type and checked before allocation or indexing. The parser returns explicit status values such as OK, BAD_LENGTH, TIMEOUT, and BAD_CHECKSUM; it does not partially update application state.

Unit tests cover zero, maximum, maximum-plus-one, truncated, duplicated, and checksum-corrupted frames. A fuzz harness generates arbitrary byte streams under a time limit. CI runs static analysis, ASan, UBSan, and regression tests. A benchmark compares the old and new implementations using valid packets and malformed traffic; the implementation uses a bounded reusable buffer rather than removing validation.

Outcome

The corrected parser rejects malformed frames without memory corruption and recovers after timeouts. Measurement showed a small average improvement because repeated allocation was eliminated, while worst-case processing became bounded. The project also documented the protocol, ownership rules, and security assumptions.

Learning points

The case demonstrates that security checks and performance engineering can reinforce one another. It also shows why a passing nominal test is weak evidence, why fuzzing needs an oracle and resource limits, and why a performance optimization that removes a safety invariant is unacceptable.

9. Challenges, Limitations, and Contemporary Practice

No tool detects every defect. Tests are limited by their inputs and oracles; coverage can encourage superficial execution; sanitizers may miss platform-specific problems; static analysis may produce false positives; and benchmarks can be unrepresentative. Formal methods can establish strong properties but require precise models and substantial expertise. Quality assurance is therefore based on converging evidence rather than a single score.

Modern development increasingly uses continuous integration, compiler hardening, fuzzing at scale, secure coding standards, and automated dependency scanning. Hardware-assisted memory tagging and control-flow protections can add defence in depth, but they do not make unsafe source code correct. Safer languages may be selected for new components while C remains necessary for existing interfaces, real-time constraints, or hardware access; mixed-language systems require especially clear ownership and calling conventions.

AI-assisted coding can generate tests, explain diagnostics, or suggest refactorings, but generated code and generated tests require human review, compilation under strict diagnostics, security analysis, licensing review, and independent testing. An AI system may reproduce common insecure patterns or confidently misunderstand undefined behaviour. It should support, not replace, engineering accountability.

Ethically, engineers must disclose serious vulnerabilities responsibly, preserve user privacy in diagnostic data, avoid concealing known failures, and balance availability with safe failure. In regulated or safety-related domains, traceability from requirement through implementation, test evidence, change control, and incident response may be mandatory.

Best-Practice Checklist

  • Define correctness, safety, security, and performance requirements before optimization.
  • Make failures reproducible and turn every important defect into a regression test.
  • Test boundaries, invalid inputs, resource exhaustion, and recovery paths.
  • Compile with strong warnings and use static analysis continuously.
  • Run appropriate sanitizers, fuzzers, and race detection in automated builds.
  • Make ownership, lifetime, capacity, units, and error handling explicit.
  • Check arithmetic before allocation, indexing, conversion, and protocol interpretation.
  • Measure representative workloads and report the baseline, environment, and variability.
  • Never trade away a safety invariant for an unmeasured performance claim.
  • Use reviewed cryptographic and security libraries rather than custom cryptography.
  • Review dependencies, build artifacts, update mechanisms, and debug interfaces.
  • Treat tools as evidence-producing aids, not as proofs of complete correctness.

Chapter Summary

Reliable C programming requires an integrated lifecycle of specification, testing, debugging, performance measurement, and security engineering. Unit, integration, system, regression, property-based, and fuzz testing expose different classes of failure, while assertions, debuggers, logs, sanitizers, and static analysers help locate causes. Coverage is useful but cannot establish correctness.

Performance work must begin with a defined metric and representative workload. Profiling should guide optimization, and speedup claims must compare equivalent executions. The C abstract machine makes undefined behaviour particularly dangerous because it invalidates both correctness and optimization assumptions. Secure C therefore emphasizes bounded memory operations, checked arithmetic, explicit ownership, validated input, synchronized shared state, and professionally reviewed cryptography.

The most effective practice is evidence based: formulate requirements, design defensible interfaces, automate tests and analyses, reproduce failures, measure changes, document assumptions, and learn from incidents. In professional systems, correctness, security, maintainability, and performance are jointly optimized rather than pursued in isolation.

Key Terms

Defect: An imperfection in code, design, or requirements.

Error: An incorrect internal program state caused by a defect.

Failure: Observable deviation from required behaviour.

Verification: Evaluation of whether implementation conforms to specification.

Validation: Evaluation of whether the system satisfies operational needs.

Unit testing: Testing an isolated function or component.

Regression testing: Repeating tests to detect unintended effects of change.

Test oracle: Method for determining whether an execution is correct.

Fuzzing: Automated generation or mutation of inputs to expose failures.

Assertion: Executable expression of an expected condition or invariant.

Sanitizer: Instrumentation tool that detects selected runtime defects.

Static analysis: Examination of source or binaries without executing all paths.

Profiling: Measurement of where time or resources are consumed.

Undefined behaviour: Behaviour for which the C standard imposes no requirements.

Integer overflow: A result outside the representable range of a numeric type.

Memory safety: Prevention of invalid access, lifetime, and ownership operations.

Data race: Conflicting unsynchronized concurrent accesses to shared data.

Threat model: Structured analysis of assets, attackers, boundaries, and impacts.

Defence in depth: Use of multiple independent protective controls.

Reproducible build: A build process that produces verifiably equivalent artifacts from controlled inputs.

Review Questions

Short Answer Questions

  1. Distinguish verification from validation in a C software project.
  2. Why is a minimal reproducer valuable during debugging?
  3. What is the difference between statement coverage and branch coverage?
  4. State two examples of undefined behaviour in C.
  5. Why should performance optimization be guided by profiling?

Long Answer Questions

  1. Explain a complete testing strategy for a C-based communication protocol, including unit, integration, fuzz, regression, and system testing.
  2. Describe how a debugger, static analyser, and runtime sanitizer provide different kinds of evidence.
  3. Discuss the relationship between memory ownership, lifetime, allocation failure, and secure C programming.
  4. Explain why high code coverage does not prove correctness or security, and propose complementary assurance practices.
  5. Develop a performance-engineering workflow for an embedded C application with latency and memory constraints.

Analytical / Application Questions

  1. A parser receives a claimed payload length and computes header + length + checksum in a 16-bit variable. Analyse the possible failures and design a safe validation sequence.
  2. A benchmark reports a 2× speedup after compiler optimization, but tests were run without sanitizers and with a non-representative input. Evaluate the claim and specify the evidence needed before deployment.
  3. Design a threat model and test plan for a firmware update component written in C. Include trust boundaries, failure handling, cryptographic responsibilities, fuzz targets, and release evidence.

Multiple Choice Questions with Answers

Question 1

Which activity primarily seeks the causal defect after an observed failure?

A. Debugging

B. Benchmarking

C. Acceptance testing

D. Compilation

Correct Answer: A. Debugging

Question 2

A test suite executes every statement but not both outcomes of a decision. Which coverage is incomplete?

A. Statement coverage

B. Branch coverage

C. Build coverage

D. Dependency coverage

Correct Answer: B. Branch coverage

Question 3

What is the principal danger of signed integer overflow in C?

A. It is always converted to floating point

B. It is defined to wrap modulo 2^N

C. It produces undefined behaviour

D. It automatically raises a hardware exception

Correct Answer: C. It produces undefined behaviour

Question 4

Which tool is specifically intended to detect many use-after-free and out-of-bounds memory errors at runtime?

A. AddressSanitizer

B. Linker

C. Formatter

D. Preprocessor

Correct Answer: A. AddressSanitizer

Question 5

If a baseline execution takes 12 ms and an optimized execution takes 8 ms, what is the speedup?

A. 0.67×

B. 1.5×

C. 4×

D. 20×

Correct Answer: B. 1.5×

Question 6

Which statement about assertions is correct?

A. They should replace all validation of hostile input

B. They are always enabled in every production build

C. They express assumptions or invariants and may be disabled

D. They prevent all memory corruption

Correct Answer: C. They express assumptions or invariants and may be disabled

Question 7

Before computing count * size for an allocation, which check is appropriate for nonzero size?

A. count > SIZE_MAX / size

B. count < SIZE_MAX * size

C. size > count + SIZE_MAX

D. count == size

Correct Answer: A. count > SIZE_MAX / size

Question 8

Why is printf(user_text) unsafe when user_text is untrusted?

A. It prevents all output

B. It treats user text as a format string

C. It always causes a memory leak

D. It converts text to binary

Correct Answer: B. It treats user text as a format string

Question 9

What is the principal purpose of fuzzing a parser?

A. To prove the parser is mathematically correct

B. To generate unusual inputs that may expose failures

C. To replace all system tests

D. To increase source-code comments

Correct Answer: B. To generate unusual inputs that may expose failures

Question 10

Which measure compares baseline and optimized execution times for the same workload?

A. Speedup = optimized time ÷ baseline time

B. Speedup = baseline time ÷ optimized time

C. Speedup = processor count ÷ baseline time

D. Speedup = coverage ÷ execution time

Correct Answer: B. Speedup = baseline time ÷ optimized time

Programming in C
Chapter 13 of 13