Skip to content

C++11 narrowing conversion error in ModbusTCP_unix.h on 32-bit Linux builds #10

@bravo-jcd

Description

@bravo-jcd

C++11 narrowing conversion error in ModbusTCP_unix.h on 32-bit Linux builds

Environment

  • ModbusLib Version: 0.4.6
  • Compiler: Clang 7.0.1
  • Platform: Linux 32-bit (i686)
  • Build System: CMake 4.0.2
  • Target: Debian 9

Description

Building ModbusLib 0.4.6 for 32-bit Linux fails due to C++11 narrowing conversion errors in src/unix/ModbusTCP_unix.h. The setTimeout() method attempts to initialize a timeval structure with uint32_t values that are implicitly narrowed to __time_t and __suseconds_t, which are long types (32-bit on i686 architecture).

This issue does not occur on 64-bit Linux builds because long is 64-bit, allowing the implicit conversion without narrowing.

Error Output

In file included from /home/webadm/Projects/3rdparty-modbuslib/ModbusLib-0.4.6/src/unix/ModbusTcpServer_unix.cpp:25:
In file included from /home/webadm/Projects/3rdparty-modbuslib/ModbusLib-0.4.6/src/unix/ModbusTcpServer_p_unix.h:5:
/home/webadm/Projects/3rdparty-modbuslib/ModbusLib-0.4.6/src/unix/ModbusTCP_unix.h:33:23: error: non-constant-expression cannot be narrowed from type 'unsigned int' to '__time_t' (aka 'long') in initializer list [-Wc++11-narrowing]
            .tv_sec = timeout / 1000,
                      ^~~~~~~~~~~~~~
/home/webadm/Projects/3rdparty-modbuslib/ModbusLib-0.4.6/src/unix/ModbusTCP_unix.h:33:23: note: insert an explicit cast to silence this issue
            .tv_sec = timeout / 1000,
                      ^~~~~~~~~~~~~~
                      static_cast<__time_t>( )
/home/webadm/Projects/3rdparty-modbuslib/ModbusLib-0.4.6/src/unix/ModbusTCP_unix.h:34:24: error: non-constant-expression cannot be narrowed from type 'unsigned int' to '__suseconds_t' (aka 'long') in initializer list [-Wc++11-narrowing]
            .tv_usec = (timeout % 1000) * 1000
                       ^~~~~~~~~~~~~~~~~~~~~~~
/home/webadm/Projects/3rdparty-modbuslib/ModbusLib-0.4.6/src/unix/ModbusTCP_unix.h:34:24: note: insert an explicit cast to silence this issue
            .tv_usec = (timeout % 1000) * 1000
                       ^~~~~~~~~~~~~~~~~~~~~~~
                       static_cast<__suseconds_t>( )
2 errors generated.

Root Cause

The issue is in src/unix/ModbusTCP_unix.h, lines 33-34, in the setTimeout() method:

inline void setTimeout(uint32_t timeout)
{
    timeval tv
    {
        .tv_sec = timeout / 1000,              // uint32_t -> __time_t (long)
        .tv_usec = (timeout % 1000) * 1000     // uint32_t -> __suseconds_t (long)
    };
    setsockopt(m_socket, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
    setsockopt(m_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
}

On 32-bit systems:

  • uint32_t is unsigned int (32 bits)
  • __time_t is long (32 bits, signed)
  • __suseconds_t is long (32 bits, signed)

C++11 forbids narrowing conversions from unsigned to signed types in initializer lists, even when they're the same size.

Proposed Fix

Add explicit casts to make the conversions explicit and compiler-compliant:

inline void setTimeout(uint32_t timeout)
{
    timeval tv
    {
        .tv_sec = static_cast<__time_t>(timeout / 1000),
        .tv_usec = static_cast<__suseconds_t>((timeout % 1000) * 1000)
    };
    setsockopt(m_socket, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
    setsockopt(m_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
}

Testing

After applying this fix:

  • ✅ Linux 32-bit build (i686) succeeds with Clang 7.0.1
  • ✅ Linux 64-bit build (x86_64) continues to work correctly
  • ✅ No functional changes - same behavior with explicit casts
  • ✅ Library created: libmodbus.a (304,894 bytes, 417 exported functions)

Build command used:

cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-linux32.cmake \
      -DCMAKE_BUILD_TYPE=Release \
      -DBUILD_SHARED_LIBS=OFF \
      -DMB_QT_ENABLED=OFF \
      ..

Impact

Affected Platforms:

  • All 32-bit Linux builds with C++11-compliant compilers
  • Any builds with -Werror=narrowing or -Wc++11-narrowing enabled

Not Affected:

  • 64-bit Linux builds (long is 64-bit, no narrowing occurs)
  • Builds with relaxed narrowing warnings

Additional Information

This is a standards-compliance issue introduced by C++11's stricter rules for initializer lists. The fix follows C++ best practices by making type conversions explicit, improving code clarity and portability.

The explicit casts are safe because:

  1. The timeout value is divided/modulated, ensuring results fit in the target types
  2. The conversion is from unsigned to signed of the same size
  3. The values represent time in milliseconds/microseconds, which are always positive and within range

fix-narrowing-conversion-32bit.patch

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions