1

TL;DR:

  • Compiler raises a warning. The behavior is undefined.
  • But since the undefined behavior works in the company's favor, trying to find a solid reason for it to be corrected (changing 100s of lines of code, need to convince the manager)

sprintf warning

'0' flag used with ‘%s’ gnu_printf format

This behavior is undefined according to this SO question/answer.

But in arm-gcc with the SW4STM32 tool chain (system workbench) the zeros are padded as needed.

MCVE

  1. For STM32

The below code is for an embedded system and for you to compile this you need the tool chain provided here. And for the remaining file please go here.

#include "stm32f4xx_hal.h"
#include "stm32f4_discovery.h"

/* Private define ------------------------------------------------------------*/
#define TXBUFFERSIZE 100

/* Definition for USARTx clock resources */
#define USARTx                           USART2
#define USARTx_CLK_ENABLE()              __USART2_CLK_ENABLE();
#define USARTx_RX_GPIO_CLK_ENABLE()      __GPIOA_CLK_ENABLE()
#define USARTx_TX_GPIO_CLK_ENABLE()      __GPIOA_CLK_ENABLE() 

#define USARTx_FORCE_RESET()             __USART2_FORCE_RESET()
#define USARTx_RELEASE_RESET()           __USART2_RELEASE_RESET()

/* Definition for USARTx Pins */
#define USARTx_TX_PIN                    GPIO_PIN_2
#define USARTx_TX_GPIO_PORT              GPIOA  
#define USARTx_TX_AF                     GPIO_AF7_USART2
#define USARTx_RX_PIN                    GPIO_PIN_3
#define USARTx_RX_GPIO_PORT              GPIOA 
#define USARTx_RX_AF                     GPIO_AF7_USART2

/* Private variables ---------------------------------------------------------*/
/* UART handler declaration */
UART_HandleTypeDef UartHandle;

/* Private function prototypes -----------------------------------------------*/
static void SystemClock_Config(void);
static void Error_Handler(void)
static void UART_Init(void);

/* Private functions ---------------------------------------------------------*/
int main(void)
{
    HAL_Init();

    /* Configure the system clock to 168 Mhz */
    SystemClock_Config();

    UART_Init();

    char pkt_buff[TXBUFFERSIZE+2];
    memset(pkt_buff, 0, sizeof(pkt_buff));
    sprintf(pkt_buff, "#%010s,%010s,%020s,%060s\r", "1", "2", "3", "4");
    if(HAL_UART_Transmit(&UartHandle, (uint8_t*)pkt_buff, TXBUFFERSIZE, 5000)!= HAL_OK)
    {
        Error_Handler();   
    }

    while(1) {

    }
}

static void UART_Init(void)
{
    /*##-1- Configure the UART peripheral ######################################*/
    /* Put the USART peripheral in the Asynchronous mode (UART Mode) */
    /* UART1 configured as follow:
    - Word Length = 8 Bits
    - Stop Bit = One Stop bit
    - Parity = None
    - BaudRate = 9600 baud
    - Hardware flow control disabled (RTS and CTS signals) */
    UartHandle.Instance        = USARTx;
    UartHandle.Init.BaudRate   = 9600;
    UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
    UartHandle.Init.StopBits   = UART_STOPBITS_1;
    UartHandle.Init.Parity     = UART_PARITY_NONE;
    UartHandle.Init.HwFlowCtl  = UART_HWCONTROL_NONE;
    UartHandle.Init.Mode       = UART_MODE_TX_RX;
    if(HAL_UART_Init(&UartHandle) != HAL_OK)
    {
        Error_Handler();
    }
}

/**
 * @brief  System Clock Configuration
 *         The system Clock is configured as follow : 
 *            System Clock source            = PLL (HSE)
 *            SYSCLK(Hz)                     = 168000000
 *            HCLK(Hz)                       = 168000000
 *            AHB Prescaler                  = 1
 *            APB1 Prescaler                 = 4
 *            APB2 Prescaler                 = 2
 *            HSE Frequency(Hz)              = 8000000
 *            PLL_M                          = 8
 *            PLL_N                          = 336
 *            PLL_P                          = 2
 *            PLL_Q                          = 7
 *            VDD(V)                         = 3.3
 *            Main regulator output voltage  = Scale1 mode
 *            Flash Latency(WS)              = 5
 * @param  None
 * @retval None
 */
static void SystemClock_Config(void)
{
    RCC_ClkInitTypeDef RCC_ClkInitStruct;
    RCC_OscInitTypeDef RCC_OscInitStruct;

    /* Enable Power Control clock */
    __PWR_CLK_ENABLE();

    /* The voltage scaling allows optimizing the power consumption when the device is 
    clocked below the maximum system frequency, to update the voltage scaling value 
    regarding system frequency refer to product datasheet.  */
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

    /* Enable HSE Oscillator and activate PLL with HSE as source */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLM = 8;
    RCC_OscInitStruct.PLL.PLLN = 336;
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
    RCC_OscInitStruct.PLL.PLLQ = 7;
    if(HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
    Error_Handler();
    }

    /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 
    clocks dividers */
    RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;  
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;  
    if(HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
    {
    Error_Handler();
    }
}

static void Error_Handler(void)
{
     while(1){}
}

The above code will transmit data over UART which is then read using a PC-USB transceiver. The output for the above code is

#0000000001,0000000002,00000000000000000003,000000000000000000000000000000000000000000000000000000000004

As you can see even though a warning was raised the code performed/behaved as the programmer wanted it to.

This is a legacy code and this error has been written into many places. And so for me to fix it many lines must be fixed. But since the code performs as expected the manager doesn't want to change the code.

Question

  • The code behaves as intended by the programmer, even though it is undefined. Will this undefined behavior change in the future breaking the code.
  • What argument can I make to make them fix this error?
clamentjohn
  • 3,417
  • 2
  • 18
  • 42
  • 3
    Can you please try to create a [***Minimal***, Complete and Verifiable Example](https://stackoverflow.com/help/mcve) to show us? Preferably containing the most minimal code needed to replicate your problem (e.g. something like `#include int main(void) { /* printf that causes the warning and nothing more, with a minimal format string */ }`). – Some programmer dude Jan 29 '19 at 06:56
  • A couple of other points: You say "NULL" when you mean the null-terminator. Those are semantically very different. `NULL` is a null *pointer* and should not be confused with the string null terminator (the character `'\0'`). You also in one instance cast an array of `char` to `char *`. That cast is not needed, the array will *decay* to a pointer to its first element (which is of type `char *`). – Some programmer dude Jan 29 '19 at 07:01
  • `%s` format specifier allows adding length and precision modifiers. The `0` in all of those format specifiers as modifiers to `%s` are simply wrong, and can/will invoke undefined behavior. At-best non-standard but implemented by your toolchain if the toolchain itself is non-standard. Are you asking whether this is a warning you can ignore for a platform that *doesn't* report them, because you received these warnings on a platform that *did* report them? – WhozCraig Jan 29 '19 at 07:03
  • Lastly, as mentioned in [a comment](https://stackoverflow.com/questions/7329399/printf-issue-in-linux#comment8838020_7329441) to the accepted answer of the question you link to (and also mentioned in [this `printf` (and family) reference](https://en.cppreference.com/w/c/io/fprintf): Using zero-padding with e.g. `"%s"` leads to [*undefined behavior*](https://en.wikipedia.org/wiki/Undefined_behavior). Just don't do things that lead to UB. – Some programmer dude Jan 29 '19 at 07:04
  • 1
    The biggest problem here is the presence of stdio.h on an embedded system. It will keep spawning bad things and bugs until removed. – Lundin Jan 29 '19 at 07:32
  • @Someprogrammerdude Ive added a MCVE. I'm sorry for not being clear enough before. – clamentjohn Jan 29 '19 at 10:21
  • @Lundin `stdin` isn't used. The previous version of the question had a non-embedded code which might have confused you. I've updated the question. Question: Since the undefined behavior is in my favor should I fix the problem? – clamentjohn Jan 29 '19 at 10:22
  • 2
    I believe this would be a better MCVE, key word being *minimal*: `char buff[100]; sprintf(buff, "%010s", "1");`. But anyway: since this is legacy code, it probably isn't covered with unit tests, it is currently working, and your manager doesn't want you to change it, I believe the answer is clear: don't modify it. – vgru Jan 29 '19 at 10:37
  • 2
    In the likely case that you won't get permission to fix this: Make sure that you have gathered evidence to prove that it's not your fault when the code eventually breaks. – user694733 Jan 29 '19 at 10:51
  • @user694733 Ha, thank you. Will do that. – clamentjohn Jan 29 '19 at 10:57
  • @clmno I said `stdio.h` not `stdin`. The presence of printf-like functions in an embedded system is always a problem. In the case of sprintf, it bloats the RAM and flash usage around 100 times more than necessary. In addition, the type safety is non-existent and these functions are dangerous to use in general. It's recommended to avoid stdio.h in production code for all applications, embedded or not. – Lundin Jan 29 '19 at 12:41
  • @Lundin Okay, thank you. I have read about shooting yourself in the foot when using string functions, but I'll keep this in mind too. – clamentjohn Jan 29 '19 at 12:48
  • @clmno sprintf will surely shoot your memory. Converting from string to string is trivial, you don't even need any library function for this. In particular not when the values are constants and you can just `memcpy` them in place in the buffer. Could probably even DMA them if you want to be fancy, but it might be more trouble than its worth. – Lundin Jan 29 '19 at 12:51

2 Answers2

2

Undefined behavior is simply that, undefined. The application can literally do anything after UB. It may be reading a value from RAM somewhere and using that to determine whether to zero pad, or it could zero pad your strings correctly but decide to completely corrupt all of your stack frames. Or silently change a subtraction to an addition in an unrelated area of code.

Undefined behavior is undefined. Anything can happen.

It was noted that "But since the undefined behavior works in the company's favor..." It appears to work. For a given set of starting conditions. For the code base as it exists at that moment. All bets are off as soon as you make a single change anywhere.

With that said, the most likely case is that the compiler has chosen to define the behavior, and that behavior happens to be the one you are expecting. In which case Jeb's answer could work for you. But personally, I would never rely on undefined behavior, regardless of how innocuous the side effects appeared.

Ross
  • 1,313
  • 4
  • 16
  • 24
  • This is a good answer. *It works now but might crash later*. Obviously there are changes being made in this legacy code, so if it may fail in the future, why take the risk. Thank you. – clamentjohn Jan 29 '19 at 14:55
1

I wouldn't change the code, because the current toolchain produces the desired behaviour (never change a running system).

But you could add somewhere a lock to the current version of the toolchain.
Something like

#if !defined(__ARMCC_VERSION) || (__ARMCC_VERSION != 4711)
#error DON'T CHANGE THE TOOLCHAIN, check if 'sprintf("%010s", "1" )' still works!
#endif

And to be sure that you don't get undefined behaviour with your toolchain, you should look into the sprintf.c/vsprintf.c source code.

jeb
  • 78,592
  • 17
  • 171
  • 225