In both cases (void m with caller expecting int, void main with caller-in-C-library expecting int), this is undefined behavior, and your compiler should have chewed you out, e.g. [GCC 4.8 with -Wall]:
test.c:3:6: warning: return type of ‘main’ is not ‘int’
 void main()
      ^
test.c: In function ‘main’:
test.c:7:5: warning: implicit declaration of function ‘m’
     int k = m();
     ^
test.c: At top level:
test.c:13:6: warning: conflicting types for ‘m’
 void m()
      ^
test.c:7:13: note: previous implicit declaration of ‘m’ was here
     int k = m();
             ^
"Implicit declarations" exist only for backward compatibility with pre-C1989 programs, which are more than 20 years out of date at this point.  If you had written this code in a more modern style you would've gotten a hard error:
#include <stdio.h>
extern void m(void);
int main(void)
{
    int k = m();
    printf("%d", k);
    return 0;
}
void m(void)
{
    printf("hello");
}
⇒
test.c: In function ‘main’:
test.c:7:13: error: void value not ignored as it ought to be
     int k = m();
             ^
The key addition is the extern void m(void); line.  The compiler isn't allowed to look ahead to the definition of m when it checks main for errors, so you need a forward declaration to tell it what m's type really is.  You should also know that in C (but not C++), void m() means "m takes an unspecified number of arguments".  To declare a function taking no arguments, which is what you wanted, you have to write void m(void).
I believe what is happening on your specific system is that the return value of printf, which your code officially ignores, is being left in the return-value register and thus interpreted as the return value of m, but you cannot rely on anything predictable happening.  That is what 'undefined behavior' means.