Convert a binary string, with each char separated by spaces, into a normal C string and print
OP: Please leave any feedback you see fit, with special attention to the loops and data types.




char type

Although char is signed or unsigned, string functions of the standard library work with the data as if it is was unsigned char.


  ... each character shall be interpreted as if it had the type unsigned char ... C17dr §7.24.1 3


Also: char output = 0; ... output |= 1 &lt;&lt; 7-i; is or-ing bits potentially into the sign place.  This really is not a concern, but in general, logical bit manipulation is best done with unsigned types.

int/unsigned, in general, is the preferred type to use for efficient/clean code.  Thus using unsigned/int rather than char output can make for better code.

Putting these ideas together: recommend something more like

char bit_string_decode(char *const str) {
    unsigned output = 0;
    size_t i;
    for(i = 0; i &lt; 8; ++i) {
        switch(str[i]) {
            case '1':
                output |= 1u &lt;&lt; 7-i;  // I prefer unsigned types for bit manipulations
                break;
            case '0':
                break;
        }
    }
    return (char) output;
}


const

bit_string_decode(char *const str) does not modify the reference data. Use const for greater functional usage, more function clarity and help weak compilers optimize. 

// bit_string_decode(char *const str)
bit_string_decode(const char *const str)
//                ^---^

// bool bit_string_is_valid(char *bit_string)
bool bit_string_is_valid(const char *bit_string)


Simplifications

Some alternative ideas to simplify the loop:

char bit_string_decode(const char *str) {
    const unsigned char *ustr = (const unsigned char *) str;
    unsigned output = 0;
    while (*(const unsigned char*)ustr) { // cast: see note below
        output &lt;&lt;= 1;
        // Given *str is '0' or '1'
        output |= *ustr++ - '0';
    }
    return (char) output;
}


Allocate to the size of the referenced data, not type

Code is easier to code right, review and maintain.

// char *output_buffer = malloc(sizeof(char)*(count+1));
char *output_buffer = malloc(sizeof *output_buffer *(count+1));
//                                  ^------------^  Referenced data


Consider decoding without destruction

binary_agent(char *bin_str) messes up bin_str due to strtok(bin_str,delim).  Use of strspn(), strcspn(), strchr() could be used to find the delimiter without changing bin_str.  This functional change I find would make for more useful code and greater applicability.

Missing validation in production

I would say strlen(bit_string) == 8 (or equivalent) should always test, not in only in assert.  Perhaps as

bool bit_string_is_valid(const char *bit_string) {
  if (bit_string == NULL) {
    return false;
  }
  const char *s = bit_string;
  while(* (const unsigned char*)s) {  // cast: see note below
    if((*s != '0') &amp;&amp; (*s != '1')) {
      return false;
    }
    s++;
  }
  return (s - bit_string) == 8;
}


The cast (const unsigned char*) is only useful here for the all but extinct non-2's complement platforms to properly ID the null character.  Drop it, unless code needs that level of portability.



Minor

Spacing

Separate function declarations from functions a bit.

bool bit_string_is_valid(char *bit_string);
// add space here
int main(void)